地圖類型

選取平台: Android iOS JavaScript

本文件探討了可透過 Maps JavaScript API 顯示的地圖類型。API 會使用 MapType 物件來保存這些地圖的相關資訊。MapType 介面可定義地圖圖塊的顯示與使用方式,以及座標系統從螢幕座標到世界座標 (在地圖上) 的翻譯。每個 MapType 都必須包含一些方法,用於處理擷取和釋出圖塊,以及定義其視覺行為的屬性。

Maps JavaScript API 中地圖類型的內部運作是進階主題。多數開發人員都能使用下方所述的基本地圖類型。不過,您也可以使用樣式化地圖修改現有地圖類型的呈現方式,也可以使用自訂地圖類型定義自己的圖塊。提供自訂地圖類型時,您必須瞭解如何修改地圖的地圖類型登錄

基本地圖類型

Maps JavaScript API 提供四種地圖類型。除了熟悉的「繪製」道路圖塊外,Maps JavaScript API 也支援其他地圖類型。

Maps JavaScript API 提供下列地圖類型:

  • roadmap 會顯示預設的道路地圖檢視。這是預設的地圖類型。
  • satellite 會顯示 Google 地球衛星影像。
  • hybrid 會顯示正常和衛星檢視的混合組合。
  • terrain 會根據地形資訊顯示實體地圖。

如要修改 Map 使用的地圖類型,方法是設定 mapTypeId 屬性,方法是在建構函式中設定 Map options 物件,或是呼叫地圖的 setMapTypeId() 方法。mapTypeID 屬性預設為 roadmap

在建構時設定 mapTypeId

var myLatlng = new google.maps.LatLng(-34.397, 150.644);
var mapOptions = {
  zoom: 8,
  center: myLatlng,
  mapTypeId: 'satellite'
};
var map = new google.maps.Map(document.getElementById('map'),
    mapOptions);

動態修改 mapTypeId

map.setMapTypeId('terrain');

請注意,您實際上並未直接設定地圖的地圖類型,而是將其 mapTypeId 設為使用 ID 參照 MapType。Maps JavaScript API 會使用地圖類型登錄方式 (如下所述) 來管理這些參照。

45° 圖像

Maps JavaScript API 支援特定位置的特殊 45° 圖像。這個高解析度圖像提供每張基本方向 (北、南、東、西) 的視角。這些圖片在支援的地圖類型中會以較高的縮放等級提供。

下圖顯示紐約市的 45 度環景:

satellitehybrid 地圖類型支援高縮放等級 (12 以上) 的 45 度角圖像 (如果有的話)。如果使用者放大包含這類圖像的位置,這些地圖類型會自動以下列方式變更其檢視畫面:

  • 衛星圖像或混合圖像會替換成以目前位置為中心的 45 度角圖像。根據預設,這類檢視畫面是以北方為方向。如果使用者縮小,預設衛星或混合圖像就會再次顯示。行為會因縮放等級和 tilt 的值而有所不同:
    • 根據預設,在縮放等級 12 到 18 之間,由上而下的基本地圖 (0°) 會顯示,除非 tilt 設為 45。
    • 除非 tilt 設為 0,否則縮放等級為 18 以上時,45° 基本地圖會顯示。
  • 旋轉控制項隨即顯示。旋轉控制項提供可讓使用者切換傾斜選項,以及將方向以 90° 的增量旋轉。如要隱藏旋轉控制項,請將 rotateControl 設為 false

縮小會顯示 45° 圖像的地圖類型時,這些變更會逐一還原,並重新建立原始的地圖類型。

啟用及停用 45° 圖像

您可以在 Map 物件上呼叫 setTilt(0) 來停用 45° 圖像。如要啟用受支援地圖類型 45 度的圖像,請呼叫 setTilt(45)MapgetTilt() 方法一律會反映地圖上目前的 tilt。如果設定地圖上的 tilt,稍後又移除該 tilt (例如縮小地圖),地圖的 getTilt() 方法會傳回 0

重要事項:45° 圖像僅支援光柵地圖,無法與向量地圖搭配使用。

以下範例顯示紐約市的 45 度角:

TypeScript

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

  map.setTilt(45);
}

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

JavaScript

function initMap() {
  const map = new google.maps.Map(document.getElementById("map"), {
    center: { lat: 40.76, lng: -73.983 },
    zoom: 15,
    mapTypeId: "satellite",
  });

  map.setTilt(45);
}

window.initMap = initMap;
查看範例

查看範例

查看範例

旋轉 45° 圖像

45° 圖像實際上包含每個基線方向 (北、南、東、西) 的一系列圖片。地圖顯示 45 度角圖像後,您可以在 Map 物件上呼叫 setHeading(),將圖像方向以北方朝向的方向為方向,並傳遞北方的度數。

以下範例說明點選按鈕後,每 3 秒自動旋轉地圖一次:

TypeScript

let map: google.maps.Map;

function initMap(): void {
  map = new google.maps.Map(document.getElementById("map") as HTMLElement, {
    center: { lat: 40.76, lng: -73.983 },
    zoom: 15,
    mapTypeId: "satellite",
    heading: 90,
    tilt: 45,
  });

  // add listener to button
  document.getElementById("rotate")!.addEventListener("click", autoRotate);
}

function rotate90(): void {
  const heading = map.getHeading() || 0;

  map.setHeading(heading + 90);
}

function autoRotate(): void {
  // Determine if we're showing aerial imagery.
  if (map.getTilt() !== 0) {
    window.setInterval(rotate90, 3000);
  }
}

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

JavaScript

let map;

function initMap() {
  map = new google.maps.Map(document.getElementById("map"), {
    center: { lat: 40.76, lng: -73.983 },
    zoom: 15,
    mapTypeId: "satellite",
    heading: 90,
    tilt: 45,
  });
  // add listener to button
  document.getElementById("rotate").addEventListener("click", autoRotate);
}

function rotate90() {
  const heading = map.getHeading() || 0;

  map.setHeading(heading + 90);
}

function autoRotate() {
  // Determine if we're showing aerial imagery.
  if (map.getTilt() !== 0) {
    window.setInterval(rotate90, 3000);
  }
}

window.initMap = initMap;
查看範例

查看範例

查看範例

修改地圖類型登錄

Map's mapTypeId 是字串 ID,可用於將 MapType 與不重複的值建立關聯。每個 Map 物件都會維護 MapTypeRegistry,其中包含該地圖的可用 MapType 集合。舉例來說,此登錄檔可用來選取可在地圖控制項中的地圖類型使用的地圖類型。

您無法直接讀取地圖類型登錄,而必須新增自訂地圖類型並將其與您選擇的字串 ID 建立關聯,以修改登錄檔。您無法修改或變更基本地圖類型 (但可以透過變更地圖相關聯的 mapTypeControlOptions 來將其從地圖中移除)。

下列程式碼會設定地圖只顯示在地圖 mapTypeControlOptions 中顯示的兩種地圖類型,並修改登錄檔,將這個 ID 的關聯新增至實際實作 MapType 介面。

// Modify the control to only display two maptypes, the
// default ROADMAP and the custom 'mymap'.
// Note that because this is an association, we
// don't need to modify the MapTypeRegistry beforehand.

var MY_MAPTYPE_ID = 'mymaps';

var mapOptions = {
  zoom: 12,
  center: brooklyn,
  mapTypeControlOptions: {
     mapTypeIds: ['roadmap', MY_MAPTYPE_ID]
  },
  mapTypeId: MY_MAPTYPE_ID
};

// Create our map. This creation will implicitly create a
// map type registry.
map = new google.maps.Map(document.getElementById('map'),
    mapOptions);

// Create your custom map type using your own code.
// (See below.)
var myMapType = new MyMapType();

// Set the registry to associate 'mymap' with the
// custom map type we created, and set the map to
// show that map type.
map.mapTypes.set(MY_MAPTYPE_ID, myMapType);

樣式化地圖

StyledMapType 可讓您自訂標準 Google 基本地圖的呈現方式,將這類元素的視覺顯示變更為道路、公園及建構區,以反映與預設地圖類型不同的樣式。

如要進一步瞭解 StyledMapType,請參閱樣式化地圖指南。

自訂地圖類型

Maps JavaScript API 支援顯示及管理自訂地圖類型,方便您實作自己的地圖圖像或圖塊疊加層。

Maps JavaScript API 提供多種可能的地圖類型實作:

  • 標準「圖塊集」,由共同構成完整的地圖地圖的圖片組成。這些圖塊集又稱為「基本」地圖類型。這些地圖類型的運作方式與現有的預設地圖類型相同:roadmapsatellitehybridterrain。您可以將自訂地圖類型新增至 Map' 的 mapTypes 陣列中,讓 Maps JavaScript API 中的 UI 將自訂地圖類型視為標準地圖類型 (例如在 Geocoding 控制項中加入該地圖類型)。
  • 圖片圖塊疊加層會在現有基本地圖類型上方顯示。一般來說,這些地圖類型是用來擴增現有的地圖類型,以顯示其他資訊,且通常只能在特定位置和/或縮放等級中使用。請注意,這些圖塊可能是透明的,可讓您將功能新增至現有地圖。
  • 非圖片地圖類型,可讓您操控地圖最基本層級的地圖資訊顯示方式。

其中每個選項都需建立實作 MapType 介面的類別。此外,ImageMapType 類別提供了一些內建行為,藉此簡化圖像地圖類型的建立作業。

MapType 介面

建立實作 MapType 的類別前,請務必先瞭解 Google 地圖如何判斷座標,並決定要在地圖上顯示哪些部分。您必須為任何基本或疊加層地圖類型實作類似的邏輯。請參閱地圖與圖塊座標指南。

自訂地圖類型必須實作 MapType 介面。這個介面會指定特定屬性和方法,讓 API 判斷需要在目前的可視區域和縮放等級內顯示地圖類型時,向地圖類型發出要求。您處理這些要求,以決定要載入的圖塊。

注意:您可以自行建立類別來實作這個介面。或者,如果您有相容的圖像,也可以使用已導入此介面的 ImageMapType 類別。

實作 MapType 介面的類別會要求您定義並填入下列屬性:

  • tileSize (必填) 指定圖塊的大小 (類型為 google.maps.Size)。大小必須是矩形,但不需要是正方形。
  • maxZoom (必填) 指定此地圖類型顯示圖塊的最大縮放等級。
  • minZoom (選用) 指定此地圖類型顯示圖塊的最低最小縮放等級。根據預設,這個值為 0,表示沒有最小縮放等級。
  • name (選用) 指定這個地圖類型的名稱。只有在您想在 Geocoding 控制項中選取這個地圖類型時,才需要這項屬性。(請參閱下方的新增 MapType 控制項一節)。
  • alt (選用) 指定此地圖類型的替代文字,以懸停文字呈現。只有在您想在 Geo 控制項中選取這個地圖類型時,才需要使用這項屬性。(請參閱下方的新增MapType控制項 )。

此外,實作 MapType 介面的類別必須實作下列方法:

  • 當 API 判定地圖需要顯示特定可視區域的新圖塊時,就會呼叫 getTile() (必要)。getTile() 方法必須具備下列簽章:

    getTile(tileCoord:Point,zoom:number,ownerDocument:Document):Node

    API 會根據 MapTypetileSizeminZoommaxZoom 屬性,以及地圖目前的可視區域和縮放等級,判斷是否需要呼叫 getTile()。這個方法的處理常式應傳回傳遞傳遞的座標、縮放等級,以及要附加圖塊圖片的 DOM 元素的 HTML 元素。

  • releaseTile() (選用) 會在 API 判定地圖從檢視範圍中移除時移除圖塊。此方法必須包含下列簽章:

    releaseTile(tile:Node)

    一般來說,除了地圖以外,您還會附加附加至地圖圖塊的任何元素。舉例來說,如果您已附加事件監聽器以對應圖塊疊加層,則應在這裡移除。

getTile() 方法可做為判斷使用者在特定可視區域中載入哪些圖塊的主要控制器。

基本地圖類型

透過這種方式建構的地圖類型可能各自獨立,或以疊加層的形式與其他地圖類型合併。獨立地圖類型稱為基本地圖類型。您可能希望 API 處理這類自訂 MapType,就如同使用現有的任何基本地圖類型 (ROADMAPTERRAIN 等) 一樣。如果要這麼做,請將自訂 MapType 新增至 MapmapTypes 屬性。這個屬性的類型是 MapTypeRegistry

以下程式碼會建立基本 MapType 以顯示地圖的圖塊座標,並繪製圖塊的外框:

TypeScript

/*
 * This demo demonstrates how to replace default map tiles with custom imagery.
 * In this case, the CoordMapType displays gray tiles annotated with the tile
 * coordinates.
 *
 * Try panning and zooming the map to see how the coordinates change.
 */

class CoordMapType {
  tileSize: google.maps.Size;
  maxZoom = 19;
  name = "Tile #s";
  alt = "Tile Coordinate Map Type";

  constructor(tileSize: google.maps.Size) {
    this.tileSize = tileSize;
  }

  getTile(
    coord: google.maps.Point,
    zoom: number,
    ownerDocument: Document
  ): HTMLElement {
    const div = ownerDocument.createElement("div");

    div.innerHTML = String(coord);
    div.style.width = this.tileSize.width + "px";
    div.style.height = this.tileSize.height + "px";
    div.style.fontSize = "10";
    div.style.borderStyle = "solid";
    div.style.borderWidth = "1px";
    div.style.borderColor = "#AAAAAA";
    div.style.backgroundColor = "#E5E3DF";
    return div;
  }

  releaseTile(tile: HTMLElement): void {}
}

function initMap(): void {
  const map = new google.maps.Map(
    document.getElementById("map") as HTMLElement,
    {
      zoom: 10,
      center: { lat: 41.85, lng: -87.65 },
      streetViewControl: false,
      mapTypeId: "coordinate",
      mapTypeControlOptions: {
        mapTypeIds: ["coordinate", "roadmap"],
        style: google.maps.MapTypeControlStyle.DROPDOWN_MENU,
      },
    }
  );

  map.addListener("maptypeid_changed", () => {
    const showStreetViewControl =
      (map.getMapTypeId() as string) !== "coordinate";

    map.setOptions({
      streetViewControl: showStreetViewControl,
    });
  });

  // Now attach the coordinate map type to the map's registry.
  map.mapTypes.set(
    "coordinate",
    new CoordMapType(new google.maps.Size(256, 256))
  );
}

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

JavaScript

/*
 * This demo demonstrates how to replace default map tiles with custom imagery.
 * In this case, the CoordMapType displays gray tiles annotated with the tile
 * coordinates.
 *
 * Try panning and zooming the map to see how the coordinates change.
 */
class CoordMapType {
  tileSize;
  maxZoom = 19;
  name = "Tile #s";
  alt = "Tile Coordinate Map Type";
  constructor(tileSize) {
    this.tileSize = tileSize;
  }
  getTile(coord, zoom, ownerDocument) {
    const div = ownerDocument.createElement("div");

    div.innerHTML = String(coord);
    div.style.width = this.tileSize.width + "px";
    div.style.height = this.tileSize.height + "px";
    div.style.fontSize = "10";
    div.style.borderStyle = "solid";
    div.style.borderWidth = "1px";
    div.style.borderColor = "#AAAAAA";
    div.style.backgroundColor = "#E5E3DF";
    return div;
  }
  releaseTile(tile) {}
}

function initMap() {
  const map = new google.maps.Map(document.getElementById("map"), {
    zoom: 10,
    center: { lat: 41.85, lng: -87.65 },
    streetViewControl: false,
    mapTypeId: "coordinate",
    mapTypeControlOptions: {
      mapTypeIds: ["coordinate", "roadmap"],
      style: google.maps.MapTypeControlStyle.DROPDOWN_MENU,
    },
  });

  map.addListener("maptypeid_changed", () => {
    const showStreetViewControl = map.getMapTypeId() !== "coordinate";

    map.setOptions({
      streetViewControl: showStreetViewControl,
    });
  });
  // Now attach the coordinate map type to the map's registry.
  map.mapTypes.set(
    "coordinate",
    new CoordMapType(new google.maps.Size(256, 256))
  );
}

window.initMap = initMap;
查看範例

查看範例

疊加層地圖類型

部分地圖類型的設計是為了在現有地圖類型上方使用。這類地圖類型可能有透明層,用來指出搜尋點或向使用者顯示額外資料。

在這些情況下,您不希望地圖類型視為疊加層,但會視為疊加層。您可以直接使用 MapoverlayMapTypes 屬性,將地圖類型新增至現有的 MapType。這個屬性包含 MapTypeMVCArray。所有地圖類型 (基本和疊加層) 都會在 mapPane 圖層內轉譯。疊加層地圖類型會以其附加的基本地圖頂端顯示,以在 Map.overlayMapTypes 陣列中的順序顯示 (索引值較高的疊加層會顯示在較低索引值的疊加層前方)。

以下範例與前述範例相同,差別在於我們在 ROADMAP 地圖類型上方建立了圖塊疊加層 MapType

TypeScript

/*
 * This demo illustrates the coordinate system used to display map tiles in the
 * API.
 *
 * Tiles in Google Maps are numbered from the same origin as that for
 * pixels. For Google's implementation of the Mercator projection, the origin
 * tile is always at the northwest corner of the map, with x values increasing
 * from west to east and y values increasing from north to south.
 *
 * Try panning and zooming the map to see how the coordinates change.
 */

class CoordMapType implements google.maps.MapType {
  tileSize: google.maps.Size;
  alt: string|null = null;
  maxZoom: number = 17;
  minZoom: number = 0;
  name: string|null = null;
  projection: google.maps.Projection|null = null;
  radius: number = 6378137;

  constructor(tileSize: google.maps.Size) {
    this.tileSize = tileSize;
  }
  getTile(
    coord: google.maps.Point,
    zoom: number,
    ownerDocument: Document
  ): HTMLElement {
    const div = ownerDocument.createElement("div");

    div.innerHTML = String(coord);
    div.style.width = this.tileSize.width + "px";
    div.style.height = this.tileSize.height + "px";
    div.style.fontSize = "10";
    div.style.borderStyle = "solid";
    div.style.borderWidth = "1px";
    div.style.borderColor = "#AAAAAA";
    return div;
  }
  releaseTile(tile: Element): void {}
}

function initMap(): void {
  const map = new google.maps.Map(
    document.getElementById("map") as HTMLElement,
    {
      zoom: 10,
      center: { lat: 41.85, lng: -87.65 },
    }
  );

  // Insert this overlay map type as the first overlay map type at
  // position 0. Note that all overlay map types appear on top of
  // their parent base map.
  const coordMapType = new CoordMapType(new google.maps.Size(256, 256))
  map.overlayMapTypes.insertAt(
    0,
    coordMapType
  );
}

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

JavaScript

/*
 * This demo illustrates the coordinate system used to display map tiles in the
 * API.
 *
 * Tiles in Google Maps are numbered from the same origin as that for
 * pixels. For Google's implementation of the Mercator projection, the origin
 * tile is always at the northwest corner of the map, with x values increasing
 * from west to east and y values increasing from north to south.
 *
 * Try panning and zooming the map to see how the coordinates change.
 */
class CoordMapType {
  tileSize;
  alt = null;
  maxZoom = 17;
  minZoom = 0;
  name = null;
  projection = null;
  radius = 6378137;
  constructor(tileSize) {
    this.tileSize = tileSize;
  }
  getTile(coord, zoom, ownerDocument) {
    const div = ownerDocument.createElement("div");

    div.innerHTML = String(coord);
    div.style.width = this.tileSize.width + "px";
    div.style.height = this.tileSize.height + "px";
    div.style.fontSize = "10";
    div.style.borderStyle = "solid";
    div.style.borderWidth = "1px";
    div.style.borderColor = "#AAAAAA";
    return div;
  }
  releaseTile(tile) {}
}

function initMap() {
  const map = new google.maps.Map(document.getElementById("map"), {
    zoom: 10,
    center: { lat: 41.85, lng: -87.65 },
  });
  // Insert this overlay map type as the first overlay map type at
  // position 0. Note that all overlay map types appear on top of
  // their parent base map.
  const coordMapType = new CoordMapType(new google.maps.Size(256, 256));

  map.overlayMapTypes.insertAt(0, coordMapType);
}

window.initMap = initMap;
查看範例

查看範例

影像地圖類型

實作 MapType 做為基本地圖類型的做法可能相當耗時費力。API 提供的特殊類別會實作最常見的地圖類型的 MapType 介面:由單一圖片檔組成的圖塊構成的地圖類型。

這個類別 ImageMapType 類別是透過定義下列必要屬性的 ImageMapTypeOptions 物件規格建構:

  • tileSize (必填) 指定圖塊的大小 (類型為 google.maps.Size)。大小必須是矩形,但不需要是正方形。
  • getTileUrl (必要) 指定函式 (通常以內嵌函式常值的形式提供),以便根據提供的世界座標和縮放等級處理適當的圖片圖塊選取項目。

以下程式碼使用 Google 的月光圖塊實作基本 ImageMapType。這個範例使用正規函式,確保圖塊在 X 軸上重複,但與地圖的 Y 軸相同。

TypeScript

function initMap(): void {
  const map = new google.maps.Map(
    document.getElementById("map") as HTMLElement,
    {
      center: { lat: 0, lng: 0 },
      zoom: 1,
      streetViewControl: false,
      mapTypeControlOptions: {
        mapTypeIds: ["moon"],
      },
    }
  );

  const moonMapType = new google.maps.ImageMapType({
    getTileUrl: function (coord, zoom): string {
      const normalizedCoord = getNormalizedCoord(coord, zoom);

      if (!normalizedCoord) {
        return "";
      }

      const bound = Math.pow(2, zoom);
      return (
        "https://mw1.google.com/mw-planetary/lunar/lunarmaps_v1/clem_bw" +
        "/" +
        zoom +
        "/" +
        normalizedCoord.x +
        "/" +
        (bound - normalizedCoord.y - 1) +
        ".jpg"
      );
    },
    tileSize: new google.maps.Size(256, 256),
    maxZoom: 9,
    minZoom: 0,
    // @ts-ignore TODO 'radius' does not exist in type 'ImageMapTypeOptions'
    radius: 1738000,
    name: "Moon",
  });

  map.mapTypes.set("moon", moonMapType);
  map.setMapTypeId("moon");
}

// Normalizes the coords that tiles repeat across the x axis (horizontally)
// like the standard Google map tiles.
function getNormalizedCoord(coord, zoom) {
  const y = coord.y;
  let x = coord.x;

  // tile range in one direction range is dependent on zoom level
  // 0 = 1 tile, 1 = 2 tiles, 2 = 4 tiles, 3 = 8 tiles, etc
  const tileRange = 1 << zoom;

  // don't repeat across y-axis (vertically)
  if (y < 0 || y >= tileRange) {
    return null;
  }

  // repeat across x-axis
  if (x < 0 || x >= tileRange) {
    x = ((x % tileRange) + tileRange) % tileRange;
  }

  return { x: x, y: y };
}

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

JavaScript

function initMap() {
  const map = new google.maps.Map(document.getElementById("map"), {
    center: { lat: 0, lng: 0 },
    zoom: 1,
    streetViewControl: false,
    mapTypeControlOptions: {
      mapTypeIds: ["moon"],
    },
  });
  const moonMapType = new google.maps.ImageMapType({
    getTileUrl: function (coord, zoom) {
      const normalizedCoord = getNormalizedCoord(coord, zoom);

      if (!normalizedCoord) {
        return "";
      }

      const bound = Math.pow(2, zoom);
      return (
        "https://mw1.google.com/mw-planetary/lunar/lunarmaps_v1/clem_bw" +
        "/" +
        zoom +
        "/" +
        normalizedCoord.x +
        "/" +
        (bound - normalizedCoord.y - 1) +
        ".jpg"
      );
    },
    tileSize: new google.maps.Size(256, 256),
    maxZoom: 9,
    minZoom: 0,
    // @ts-ignore TODO 'radius' does not exist in type 'ImageMapTypeOptions'
    radius: 1738000,
    name: "Moon",
  });

  map.mapTypes.set("moon", moonMapType);
  map.setMapTypeId("moon");
}

// Normalizes the coords that tiles repeat across the x axis (horizontally)
// like the standard Google map tiles.
function getNormalizedCoord(coord, zoom) {
  const y = coord.y;
  let x = coord.x;
  // tile range in one direction range is dependent on zoom level
  // 0 = 1 tile, 1 = 2 tiles, 2 = 4 tiles, 3 = 8 tiles, etc
  const tileRange = 1 << zoom;

  // don't repeat across y-axis (vertically)
  if (y < 0 || y >= tileRange) {
    return null;
  }

  // repeat across x-axis
  if (x < 0 || x >= tileRange) {
    x = ((x % tileRange) + tileRange) % tileRange;
  }
  return { x: x, y: y };
}

window.initMap = initMap;
查看範例

查看範例

預測結果

「地球」是近似的 3D 球體,而地圖則是 2D 平面。在 Maps JavaScript API 中看到的地圖,就像地球的任何平面地圖一樣,是該球體在平面上的「投影」。簡單來說,投影可定義為在經緯度地圖上對應的經緯度值。

Maps JavaScript API 中的投影必須實作 Projection 介面。Projection 實作不僅必須提供從一個座標系統到另一個座標系統之間的對應,同時也是雙向對應。也就是說,您必須定義如何從地球座標 (LatLng 物件) 轉譯為 Projection 類別的世界座標系統,反之亦然。Google 地圖採用麥卡托投影功能,以地理資料建立地圖,並將地圖上的事件轉換成地理座標。您可以針對 Map (或任何標準基本 MapType 類型) 呼叫 getProjection(),取得這項投影。對大多數用途而言,這個標準的 Projection 就足以滿足這一點,但您也可以定義及使用自己的自訂投影功能。

投影實作

實作自訂投影時,您必須定義下列事項:

  • 將經緯度座標對應至笛卡兒平面的公式,反之亦然。(Projection 介面僅支援轉換為直線座標)。
  • 基本地圖方塊大小。所有地圖方塊都必須是矩形。
  • 使用縮放等級為 0 的基本圖塊的地圖。「世界大小」。 請注意,如果地圖包含一個縮放等級 0 的圖塊,世界大小和基本圖塊的大小就會相同。

投影中的座標轉換

每個投影都提供兩種方法,可在兩個座標系統之間進行平移,以便在地理和世界座標之間進行轉換:

  • Projection.fromLatLngToPoint() 方法會將 LatLng 值轉換為世界座標。此方法是用於在地圖上放置疊加層 (和定位本身)。
  • Projection.fromPointToLatLng() 方法會將世界座標轉換為 LatLng 值。此方法可用於將事件 (例如在地圖上點擊的點擊) 轉換成地理座標。

Google 地圖假設投影為直線式。

一般來說,您可以針對以下兩種情況使用投影:建立世界地圖或建立當地地圖。在先前的範例中,您應確定投影在所有矩形的矩形均正常。某些投影 (尤其是錐形投影) 可能是「本地正常」的 (也就是北方),但偏離了正北;舉例來說,地圖更偏向某些參照經度。您可以在本機使用這類投影,但請注意,投影往往不太準確,且轉換偏差在經過明顯的參照經度附近會變得越來越明顯。

投影中的地圖方塊選擇項目

投影不僅可用於判斷位置或疊加層的位置,也可用於定位地圖圖塊。Maps JavaScript API 會使用 MapType 介面算繪基本地圖,因此必須宣告 projection 屬性來識別地圖投影,還有 getTile() 方法,以便根據圖塊座標值擷取地圖圖塊。圖塊座標會以基本圖塊大小 (必須是矩形) 和「世界大小」(也就是縮放等級為 0 的地圖世界大小) 為基礎。(如果縮放等級為 0 的一個圖塊,圖塊大小和世界大小會相同)。

請在 MapType'tileSize 屬性中定義基本圖塊大小。您可以在投影的 fromLatLngToPoint()fromPointToLatLng() 方法中以隱含方式定義世界大小。

由於圖片選項取決於傳遞的值,因此為這些傳遞值命名 (例如 map_zoom_tileX_tileY.png) 時,可以使用程式為圖片命名。

以下範例使用 Gall-Peters 投影方式定義 ImageMapType

TypeScript

// This example defines an image map type using the Gall-Peters
// projection.
// https://en.wikipedia.org/wiki/Gall%E2%80%93Peters_projection

function initMap(): void {
  // Create a map. Use the Gall-Peters map type.
  const map = new google.maps.Map(
    document.getElementById("map") as HTMLElement,
    {
      zoom: 0,
      center: { lat: 0, lng: 0 },
      mapTypeControl: false,
    }
  );

  initGallPeters();
  map.mapTypes.set("gallPeters", gallPetersMapType);
  map.setMapTypeId("gallPeters");

  // Show the lat and lng under the mouse cursor.
  const coordsDiv = document.getElementById("coords") as HTMLElement;

  map.controls[google.maps.ControlPosition.TOP_CENTER].push(coordsDiv);
  map.addListener("mousemove", (event: google.maps.MapMouseEvent) => {
    coordsDiv.textContent =
      "lat: " +
      Math.round(event.latLng!.lat()) +
      ", " +
      "lng: " +
      Math.round(event.latLng!.lng());
  });

  // Add some markers to the map.
  map.data.setStyle((feature) => {
    return {
      title: feature.getProperty("name"),
      optimized: false,
    };
  });
  map.data.addGeoJson(cities);
}

let gallPetersMapType;

function initGallPeters() {
  const GALL_PETERS_RANGE_X = 800;
  const GALL_PETERS_RANGE_Y = 512;

  // Fetch Gall-Peters tiles stored locally on our server.
  gallPetersMapType = new google.maps.ImageMapType({
    getTileUrl: function (coord, zoom) {
      const scale = 1 << zoom;

      // Wrap tiles horizontally.
      const x = ((coord.x % scale) + scale) % scale;

      // Don't wrap tiles vertically.
      const y = coord.y;

      if (y < 0 || y >= scale) return "";

      return (
        "https://developers.google.com/maps/documentation/" +
        "javascript/examples/full/images/gall-peters_" +
        zoom +
        "_" +
        x +
        "_" +
        y +
        ".png"
      );
    },
    tileSize: new google.maps.Size(GALL_PETERS_RANGE_X, GALL_PETERS_RANGE_Y),
    minZoom: 0,
    maxZoom: 1,
    name: "Gall-Peters",
  });

  // Describe the Gall-Peters projection used by these tiles.
  gallPetersMapType.projection = {
    fromLatLngToPoint: function (latLng) {
      const latRadians = (latLng.lat() * Math.PI) / 180;
      return new google.maps.Point(
        GALL_PETERS_RANGE_X * (0.5 + latLng.lng() / 360),
        GALL_PETERS_RANGE_Y * (0.5 - 0.5 * Math.sin(latRadians))
      );
    },
    fromPointToLatLng: function (point, noWrap) {
      const x = point.x / GALL_PETERS_RANGE_X;
      const y = Math.max(0, Math.min(1, point.y / GALL_PETERS_RANGE_Y));

      return new google.maps.LatLng(
        (Math.asin(1 - 2 * y) * 180) / Math.PI,
        -180 + 360 * x,
        noWrap
      );
    },
  };
}

// GeoJSON, describing the locations and names of some cities.
const cities = {
  type: "FeatureCollection",
  features: [
    {
      type: "Feature",
      geometry: { type: "Point", coordinates: [-87.65, 41.85] },
      properties: { name: "Chicago" },
    },
    {
      type: "Feature",
      geometry: { type: "Point", coordinates: [-149.9, 61.218] },
      properties: { name: "Anchorage" },
    },
    {
      type: "Feature",
      geometry: { type: "Point", coordinates: [-99.127, 19.427] },
      properties: { name: "Mexico City" },
    },
    {
      type: "Feature",
      geometry: { type: "Point", coordinates: [-0.126, 51.5] },
      properties: { name: "London" },
    },
    {
      type: "Feature",
      geometry: { type: "Point", coordinates: [28.045, -26.201] },
      properties: { name: "Johannesburg" },
    },
    {
      type: "Feature",
      geometry: { type: "Point", coordinates: [15.322, -4.325] },
      properties: { name: "Kinshasa" },
    },
    {
      type: "Feature",
      geometry: { type: "Point", coordinates: [151.207, -33.867] },
      properties: { name: "Sydney" },
    },
    {
      type: "Feature",
      geometry: { type: "Point", coordinates: [0, 0] },
      properties: { name: "0°N 0°E" },
    },
  ],
};

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

JavaScript

// This example defines an image map type using the Gall-Peters
// projection.
// https://en.wikipedia.org/wiki/Gall%E2%80%93Peters_projection
function initMap() {
  // Create a map. Use the Gall-Peters map type.
  const map = new google.maps.Map(document.getElementById("map"), {
    zoom: 0,
    center: { lat: 0, lng: 0 },
    mapTypeControl: false,
  });

  initGallPeters();
  map.mapTypes.set("gallPeters", gallPetersMapType);
  map.setMapTypeId("gallPeters");

  // Show the lat and lng under the mouse cursor.
  const coordsDiv = document.getElementById("coords");

  map.controls[google.maps.ControlPosition.TOP_CENTER].push(coordsDiv);
  map.addListener("mousemove", (event) => {
    coordsDiv.textContent =
      "lat: " +
      Math.round(event.latLng.lat()) +
      ", " +
      "lng: " +
      Math.round(event.latLng.lng());
  });
  // Add some markers to the map.
  map.data.setStyle((feature) => {
    return {
      title: feature.getProperty("name"),
      optimized: false,
    };
  });
  map.data.addGeoJson(cities);
}

let gallPetersMapType;

function initGallPeters() {
  const GALL_PETERS_RANGE_X = 800;
  const GALL_PETERS_RANGE_Y = 512;

  // Fetch Gall-Peters tiles stored locally on our server.
  gallPetersMapType = new google.maps.ImageMapType({
    getTileUrl: function (coord, zoom) {
      const scale = 1 << zoom;
      // Wrap tiles horizontally.
      const x = ((coord.x % scale) + scale) % scale;
      // Don't wrap tiles vertically.
      const y = coord.y;

      if (y < 0 || y >= scale) return "";
      return (
        "https://developers.google.com/maps/documentation/" +
        "javascript/examples/full/images/gall-peters_" +
        zoom +
        "_" +
        x +
        "_" +
        y +
        ".png"
      );
    },
    tileSize: new google.maps.Size(GALL_PETERS_RANGE_X, GALL_PETERS_RANGE_Y),
    minZoom: 0,
    maxZoom: 1,
    name: "Gall-Peters",
  });
  // Describe the Gall-Peters projection used by these tiles.
  gallPetersMapType.projection = {
    fromLatLngToPoint: function (latLng) {
      const latRadians = (latLng.lat() * Math.PI) / 180;
      return new google.maps.Point(
        GALL_PETERS_RANGE_X * (0.5 + latLng.lng() / 360),
        GALL_PETERS_RANGE_Y * (0.5 - 0.5 * Math.sin(latRadians))
      );
    },
    fromPointToLatLng: function (point, noWrap) {
      const x = point.x / GALL_PETERS_RANGE_X;
      const y = Math.max(0, Math.min(1, point.y / GALL_PETERS_RANGE_Y));
      return new google.maps.LatLng(
        (Math.asin(1 - 2 * y) * 180) / Math.PI,
        -180 + 360 * x,
        noWrap
      );
    },
  };
}

// GeoJSON, describing the locations and names of some cities.
const cities = {
  type: "FeatureCollection",
  features: [
    {
      type: "Feature",
      geometry: { type: "Point", coordinates: [-87.65, 41.85] },
      properties: { name: "Chicago" },
    },
    {
      type: "Feature",
      geometry: { type: "Point", coordinates: [-149.9, 61.218] },
      properties: { name: "Anchorage" },
    },
    {
      type: "Feature",
      geometry: { type: "Point", coordinates: [-99.127, 19.427] },
      properties: { name: "Mexico City" },
    },
    {
      type: "Feature",
      geometry: { type: "Point", coordinates: [-0.126, 51.5] },
      properties: { name: "London" },
    },
    {
      type: "Feature",
      geometry: { type: "Point", coordinates: [28.045, -26.201] },
      properties: { name: "Johannesburg" },
    },
    {
      type: "Feature",
      geometry: { type: "Point", coordinates: [15.322, -4.325] },
      properties: { name: "Kinshasa" },
    },
    {
      type: "Feature",
      geometry: { type: "Point", coordinates: [151.207, -33.867] },
      properties: { name: "Sydney" },
    },
    {
      type: "Feature",
      geometry: { type: "Point", coordinates: [0, 0] },
      properties: { name: "0°N 0°E" },
    },
  ],
};

window.initMap = initMap;
查看範例

查看範例