地圖類型

選取平台: 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 會根據地形資訊顯示實際地圖。

您可以透過設定 mapTypeId 屬性,修改 Map 使用的地圖類型,具體作法是在建構函式中設定 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');

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

45° 圖像

Maps JavaScript API 可呈現特定位置的特殊 45° 圖像。這張高解析度圖像提供朝向每個方位 (東西南北) 的透視檢視畫面。在支援的地圖類型中,這些影像都能提供更高的縮放等級。

下圖顯示紐約市的 45° 透視檢視畫面:

satellitehybrid 地圖類型支援高倍縮放等級 (12 以上) 的 45° 圖像 (如有提供)。如果使用者放大的地點包含這類圖像,這些地圖類型會透過下列方式自動變更檢視畫面:

  • 衛星圖像和混合圖像會由 45° 透視圖像取代,並以目前地點為中心。這類檢視畫面預設為朝向北方。如果使用者縮小地圖,預設衛星圖像或混合圖像就會再次顯示。地圖行為會因縮放等級和 tilt 的值而有所不同:
    • 根據預設,縮放等級介於 12 到 18 時,除非 tilt 設為 45,否則會顯示俯瞰的基本地圖 (0°)。
    • 縮放等級為 18 以上時,除非 tilt 設為 0,否則會顯示 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;
查看範例

試用範例

查看範例

修改地圖類型登錄

地圖的 mapTypeId 是字串 ID,可用於將 MapType 與唯一值建立關聯。每個 Map 物件都有一個 MapTypeRegistry,其中包含一組該地圖可使用的 MapType。這項登錄可用來選取地圖類型,例如地圖的 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) 相同。您可以將自訂地圖類型新增至地圖的 mapTypes 陣列中,這樣就能讓 Maps JavaScript API 中的使用者介面,將自訂地圖類型視為標準地圖類型 (例如透過將自訂地圖類型加進 MapType 控制項中)。
  • 影像圖塊「疊加層」,顯示在現有基本地圖類型上方。一般而言,這種地圖類型是用來補充現有地圖類型不足的部分,可顯示額外資訊且範圍通常限於特定位置和/或縮放等級。請注意,這些圖塊可能是透明圖塊,以便您在現有地圖上新增地圖項目。
  • 非影像式地圖類型,您可以在最基礎的層級處理地圖資訊的呈現方式。

您必須建立用於導入 MapType 介面的類別,才能使用上述所有選項。此外,ImageMapType 類別提供一些內建行為,可以簡化圖像地圖類型的建立過程。

MapType 介面

請務必先瞭解 Google 地圖如何判斷座標,以及決定要顯示地圖的哪些部分,再建立導入 MapType 的類別。您必須針對任何基本或疊加層地圖類型導入類似的邏輯。請參閱地圖與圖塊座標指南。

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

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

導入 MapType 介面的類別必須定義並填入下列屬性:

  • tileSize (必要) 指定圖塊尺寸 (類型為 google.maps.Size)。尺寸必須是矩形,但不必是正方形。
  • maxZoom (必要) 指定顯示此地圖類型圖塊的最大縮放等級。
  • minZoom (選用) 指定顯示此地圖類型圖塊的最小縮放等級。此值預設為 0,表示沒有最小縮放等級。
  • name (選用) 指定此地圖類型的名稱。只有當您希望能夠從 MapType 控制項中選取此地圖類型時,才需要這項屬性 (請參閱下方的新增 MapType 控制項)。
  • alt (選用) 指定滑鼠懸停時此地圖類型顯示的替代文字。只有當您希望能夠從 MapType 控制項中選取此地圖類型時,才需要這項屬性 (請參閱下方的新增MapType控制項)。

此外,導入 MapType 介面的類別需要採用下列方法:

  • getTile() (必要):API 判斷地圖需要在特定可視區域中顯示新圖塊時,就會呼叫這個方法。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 且由一個圖塊組成,則地圖的圖塊尺寸與世界尺寸相同)。

基本圖塊尺寸可以在 MapTypetileSize 屬性中定義。您可以在投影的 fromLatLngToPoint()fromPointToLatLng() 方法中,隱含定義世界尺寸。

系統是根據傳遞的值選取圖片,因此建議您根據這些傳遞的值 (如 map_zoom_tileX_tileY.png),為可透過程式輔助方式選取的圖片命名。

以下範例使用高爾-彼得斯投影定義 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;
查看範例

試用範例