Loại bản đồ

Chọn nền tảng: Android iOS JavaScript

Tài liệu này thảo luận về các loại bản đồ mà bạn có thể hiển thị bằng Maps JavaScript API. API này sử dụng đối tượng MapType để lưu giữ thông tin về các bản đồ này. MapType là một giao diện xác định cách hiển thị và sử dụng các ô bản đồ cũng như việc dịch hệ toạ độ từ toạ độ màn hình sang toạ độ thế giới (trên bản đồ). Mỗi MapType phải chứa một vài phương thức để xử lý việc truy xuất và phát hành thẻ thông tin, cũng như các thuộc tính xác định hành vi hình ảnh của thẻ thông tin.

Cách hoạt động bên trong của các loại bản đồ trong Maps JavaScript API là một chủ đề nâng cao. Hầu hết nhà phát triển đều có thể sử dụng các loại bản đồ cơ bản được nêu dưới đây. Tuy nhiên, bạn cũng có thể sửa đổi cách trình bày của các loại bản đồ hiện có bằng cách sử dụng bản đồ phong cách riêng hoặc xác định các ô bản đồ của riêng bạn bằng cách sử dụng các loại bản đồ tuỳ chỉnh. Khi cung cấp các loại bản đồ tuỳ chỉnh, bạn cần hiểu cách sửa đổi Sổ đăng ký loại bản đồ của bản đồ.

Các loại bản đồ cơ bản

Có 4 loại bản đồ trong Maps JavaScript API. Ngoài các ô bản đồ đường "được vẽ" quen thuộc, API JavaScript của Maps cũng hỗ trợ các loại bản đồ khác.

Maps JavaScript API có các loại bản đồ sau:

  • roadmap hiển thị chế độ xem bản đồ đường mặc định có nhãn bản đồ cơ sở.
  • satellite hiển thị bản đồ chân thực dựa trên hình ảnh từ trên không.
  • hybrid hiển thị chế độ xem bản đồ vệ tinh có nhãn bản đồ cơ sở.
  • terrain hiển thị bản đồ vật lý dựa trên thông tin địa hình.

Bạn sửa đổi loại bản đồ mà Map sử dụng bằng cách đặt thuộc tính mapTypeId của loại bản đồ đó, trong hàm khởi tạo bằng cách đặt đối tượng Map options hoặc bằng cách gọi phương thức setMapTypeId() của bản đồ. Thuộc tính mapTypeID mặc định là roadmap.

Đặt mapTypeId khi xây dựng:

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

Sửa đổi mapTypeId một cách linh động:

map.setMapTypeId('terrain');

Xin lưu ý rằng bạn không trực tiếp đặt loại bản đồ của bản đồ, mà thay vào đó, hãy đặt mapTypeId của bản đồ để tham chiếu đến MapType bằng giá trị nhận dạng. Maps JavaScript API sử dụng một sổ đăng ký loại bản đồ (được giải thích bên dưới) để quản lý các tệp tham chiếu này.

Bản đồ 3D

Maps JavaScript API hỗ trợ hình ảnh 3D. Nhiều thao tác và tính năng tuỳ chỉnh có sẵn cho bản đồ 2D cũng có sẵn cho bản đồ 3D.

Tìm hiểu thêm về bản đồ 3D trong phần Tổng quan về bản đồ 3D.

Ví dụ sau đây cho thấy chế độ xem phối cảnh 3D của San Francisco:

Tìm hiểu thêm về Bản đồ 3D trong bài viết Tổng quan về bản đồ 3D.

Hình ảnh 45° (không dùng nữa)

Mẫu sau đây cho thấy cách đạt được chế độ xem phối cảnh 45° của San Francisco bằng cách sử dụng bản đồ 3D:

TypeScript

async function init() {
    const { Map3DElement } = await google.maps.importLibrary('maps3d');
    const map = new Map3DElement({
        center: {
            lat: 37.789,
            lng: -122.401,
            altitude: 0,
        },
        range: 2200,
        tilt: 45,
        heading: 188,
    });
    map.mode = 'SATELLITE';
    document.body.append(map);
}

void init();

JavaScript

async function init() {
    const { Map3DElement } = await google.maps.importLibrary('maps3d');
    const map = new Map3DElement({
        center: {
            lat: 37.789,
            lng: -122.401,
            altitude: 0,
        },
        range: 2200,
        tilt: 45,
        heading: 188,
    });
    map.mode = 'SATELLITE';
    document.body.append(map);
}

void init();

CSS

/* 
 * Always set the map height explicitly to define the size of the div element
 * that contains the map. 
 */
#gmp-map-3d {
    height: 100%;
}
html,
body {
    height: 100%;
    margin: 0;
    padding: 0;
}

HTML

<html>
    <head>
        <title>3d map 45-degree perspective</title>
        <link rel="stylesheet" type="text/css" href="./style.css" />
        <script type="module" src="./index.js"></script>
        <script>
            // prettier-ignore
            (g=>{var h,a,k,p="The Google Maps JavaScript API",c="google",l="importLibrary",q="__ib__",m=document,b=window;b=b[c]||(b[c]={});var d=b.maps||(b.maps={}),r=new Set,e=new URLSearchParams,u=()=>h||(h=new Promise(async(f,n)=>{await (a=m.createElement("script"));e.set("libraries",[...r]+"");for(k in g)e.set(k.replace(/[A-Z]/g,t=>"_"+t[0].toLowerCase()),g[k]);e.set("callback",c+".maps."+q);a.src=`https://maps.${c}apis.com/maps/api/js?`+e;d[q]=f;a.onerror=()=>h=n(Error(p+" could not load."));a.nonce=m.querySelector("script[nonce]")?.nonce||"";m.head.append(a)}));d[l]?console.warn(p+" only loads once. Ignoring:",g):d[l]=(f,...n)=>r.add(f)&&u().then(()=>d[l](f,...n))})({
                key: "GOOGLE_MAPS_API_KEY"
            });
        </script>
    </head>
    <body>
        <div id="map"></div>
    </body>
</html>
Xem ví dụ

Sửa đổi sổ đăng ký loại bản đồ

mapTypeId của bản đồ là một giá trị nhận dạng chuỗi dùng để liên kết MapType với một giá trị duy nhất. Mỗi đối tượng Map duy trì một MapTypeRegistry chứa tập hợp các MapType có sẵn cho bản đồ đó. Ví dụ: sổ đăng ký này được dùng để chọn các loại bản đồ có trong chế độ điều khiển MapType của Bản đồ.

Bạn không đọc trực tiếp từ sổ đăng ký loại bản đồ. Thay vào đó, bạn sửa đổi sổ đăng ký bằng cách thêm các loại bản đồ tuỳ chỉnh và liên kết các loại bản đồ đó với một giá trị nhận dạng chuỗi mà bạn chọn. Bạn không thể sửa đổi hoặc thay đổi các loại bản đồ cơ bản (mặc dù bạn có thể xoá các loại bản đồ đó khỏi bản đồ bằng cách thay đổi giao diện của mapTypeControlOptions liên kết với bản đồ).

Đoạn mã sau đây đặt bản đồ chỉ hiển thị hai loại bản đồ trong mapTypeControlOptions của bản đồ và sửa đổi sổ đăng ký để thêm mối liên kết với giá trị nhận dạng này vào quá trình triển khai thực tế của giao diện 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);

Bản đồ được tạo mẫu

StyledMapType cho phép bạn tuỳ chỉnh cách trình bày của bản đồ cơ sở chuẩn của Google, thay đổi cách hiển thị trực quan của các thành phần như đường, công viên và khu vực xây dựng để phản ánh một kiểu khác với kiểu được dùng trong loại bản đồ mặc định. StyledMapType chỉ ảnh hưởng đến loại bản đồ roadmap mặc định.

Để biết thêm thông tin về StyledMapType, hãy xem phần Sử dụng nội dung khai báo kiểu JSON được nhúng.

Các loại bản đồ tuỳ chỉnh

Maps JavaScript API hỗ trợ việc hiển thị và quản lý các loại bản đồ tuỳ chỉnh, cho phép bạn triển khai hình ảnh bản đồ hoặc lớp phủ ô của riêng mình.

Có một số cách triển khai loại bản đồ có thể có trong Maps JavaScript API:

  • Tập hợp ô chuẩn bao gồm các hình ảnh cùng nhau tạo thành bản đồ hoàn chỉnh. Những nhóm ô này còn được gọi là loại bản đồ cơ sở. Các loại bản đồ này hoạt động và có hành vi giống như các loại bản đồ mặc định hiện có: roadmap, satellite, hybrid và terrain. Bạn có thể thêm loại bản đồ tuỳ chỉnh vào mảng mapTypes của Bản đồ để cho phép giao diện người dùng trong API JavaScript của Maps xử lý loại bản đồ tuỳ chỉnh của bạn dưới dạng loại bản đồ chuẩn (ví dụ: bằng cách đưa loại bản đồ đó vào thành phần điều khiển MapType).
  • Lớp phủ ô hình ảnh hiển thị trên các loại bản đồ cơ sở hiện có. Nhìn chung, các loại bản đồ này được dùng để bổ sung cho một loại bản đồ hiện có nhằm hiển thị thêm thông tin và thường bị ràng buộc với các vị trí và/hoặc mức thu phóng cụ thể. Xin lưu ý rằng các ô này có thể trong suốt, cho phép bạn thêm các tính năng vào bản đồ hiện có.
  • Các loại bản đồ không phải bản đồ hình ảnh, cho phép bạn thao tác hiển thị thông tin bản đồ ở mức cơ bản nhất.

Mỗi lựa chọn này đều dựa vào việc tạo một lớp triển khai giao diện MapType. Ngoài ra, lớp ImageMapType còn cung cấp một số hành vi tích hợp sẵn để đơn giản hoá việc tạo các loại bản đồ hình ảnh.

Giao diện MapType

Trước khi tạo các lớp triển khai MapType, bạn cần hiểu cách Google Maps xác định toạ độ và quyết định phần nào của bản đồ sẽ hiển thị. Bạn cần triển khai logic tương tự cho mọi loại bản đồ cơ sở hoặc lớp phủ. Đọc hướng dẫn về toạ độ bản đồ và toạ độ ô vuông.

Các loại bản đồ tuỳ chỉnh phải triển khai giao diện MapType. Giao diện này chỉ định một số thuộc tính và phương thức nhất định cho phép API bắt đầu các yêu cầu đến(các) loại bản đồ của bạn khi API xác định rằng cần hiển thị các ô bản đồ trong khung nhìn và mức thu phóng hiện tại. Bạn xử lý các yêu cầu này để quyết định tải thẻ thông tin nào.

Lưu ý: Bạn có thể tạo lớp của riêng mình để triển khai giao diện này. Ngoài ra, nếu có hình ảnh tương thích, bạn có thể sử dụng lớp ImageMapType đã triển khai giao diện này.

Các lớp triển khai giao diện MapType yêu cầu bạn xác định và điền sẵn các thuộc tính sau:

  • tileSize (bắt buộc) chỉ định kích thước của ô (thuộc loại google.maps.Size). Kích thước phải là hình chữ nhật, mặc dù không nhất thiết phải là hình vuông.
  • maxZoom (bắt buộc) chỉ định mức thu phóng tối đa để hiển thị các ô của loại bản đồ này.
  • minZoom (không bắt buộc) chỉ định mức thu phóng tối thiểu để hiển thị ô của loại bản đồ này. Theo mặc định, giá trị này là 0, cho biết không có mức thu phóng tối thiểu.
  • name (không bắt buộc) chỉ định tên cho loại bản đồ này. Bạn chỉ cần thuộc tính này nếu muốn loại bản đồ này có thể chọn được trong chế độ điều khiển MapType. (Xem Tuỳ chọn điều khiển.)
  • alt (không bắt buộc) chỉ định văn bản thay thế cho loại bản đồ này, được hiển thị dưới dạng văn bản khi di chuột qua. Bạn chỉ cần thuộc tính này nếu muốn loại bản đồ này có thể chọn được trong một chế độ điều khiển MapType. (Xem Các lựa chọn kiểm soát.)

Ngoài ra, các lớp triển khai giao diện MapType cần triển khai các phương thức sau:

  • getTile() (bắt buộc) được gọi bất cứ khi nào API xác định rằng bản đồ cần hiển thị các ô mới cho khung nhìn đã cho. Phương thức getTile() phải có chữ ký sau:

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

    API này xác định xem có cần gọi getTile() hay không dựa trên các thuộc tính tileSize, minZoom và maxZoom của MapType, cũng như khung nhìn và mức thu phóng hiện tại của bản đồ. Trình xử lý cho phương thức này sẽ trả về một phần tử HTML dựa trên toạ độ, mức thu phóng và phần tử DOM được truyền vào để thêm hình ảnh ô bản đồ.

  • releaseTile() (không bắt buộc) được gọi bất cứ khi nào API xác định rằng bản đồ cần xoá một ô khi ô đó nằm ngoài tầm nhìn. Phương thức này phải có chữ ký sau:

    releaseTile(tile:Node)

    Thông thường, bạn nên xử lý việc xoá mọi phần tử được đính kèm vào các ô bản đồ khi thêm vào bản đồ. Ví dụ: nếu bạn đính kèm trình nghe sự kiện vào lớp phủ ô bản đồ, thì bạn nên xoá các trình nghe đó tại đây.

Phương thức getTile() đóng vai trò là trình điều khiển chính để xác định những ô cần tải trong một khung nhìn nhất định.

Loại bản đồ cơ sở

Các loại bản đồ mà bạn tạo theo cách này có thể đứng riêng lẻ hoặc kết hợp với các loại bản đồ khác dưới dạng lớp phủ. Các loại bản đồ độc lập được gọi là loại bản đồ cơ sở. Bạn có thể muốn API xử lý các MapType tuỳ chỉnh đó như bất kỳ loại bản đồ cơ sở hiện có nào khác (ROADMAP, TERRAIN, v.v.). Để làm như vậy, hãy thêm MapType tuỳ chỉnh vào thuộc tính mapTypes của Map. Thuộc tính này thuộc loại MapTypeRegistry.

Mã sau đây tạo một MapType cơ sở để hiển thị toạ độ ô của bản đồ và vẽ đường viền của các ô:

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;
Xem ví dụ

Loại bản đồ lớp phủ

Một số loại bản đồ được thiết kế để hoạt động trên các loại bản đồ hiện có. Những loại bản đồ như vậy có thể có các lớp trong suốt cho biết các điểm yêu thích hoặc hiển thị thêm dữ liệu cho người dùng.

Trong những trường hợp này, bạn không muốn loại bản đồ được coi là một thực thể riêng biệt mà là một lớp phủ. Bạn có thể thực hiện việc này bằng cách thêm trực tiếp loại bản đồ vào MapType hiện có bằng cách sử dụng thuộc tính overlayMapTypes của Map. Thuộc tính này chứa một MVCArray của MapType. Tất cả các loại bản đồ (bản đồ cơ sở và lớp phủ) đều được kết xuất trong lớp mapPane. Các loại bản đồ lớp phủ sẽ hiển thị trên bản đồ cơ sở mà chúng được đính kèm, theo thứ tự xuất hiện trong mảng Map.overlayMapTypes (các lớp phủ có giá trị chỉ mục cao hơn sẽ hiển thị trước các lớp phủ có giá trị chỉ mục thấp hơn).

Ví dụ sau đây giống hệt với ví dụ trước, ngoại trừ việc chúng tôi đã tạo một lớp phủ thông tin MapType trên loại bản đồ ROADMAP:

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;
Xem ví dụ

Loại bản đồ hình ảnh

Việc triển khai MapType để đóng vai trò là loại bản đồ cơ sở có thể tốn nhiều thời gian và công sức. API này cung cấp một lớp đặc biệt triển khai giao diện MapType cho các loại bản đồ phổ biến nhất: loại bản đồ bao gồm các ô được tạo thành từ các tệp hình ảnh đơn lẻ.

Lớp này, lớp ImageMapType, được tạo bằng cách sử dụng quy cách đối tượng ImageMapTypeOptions xác định các thuộc tính bắt buộc sau:

  • tileSize (bắt buộc) chỉ định kích thước của ô (thuộc loại google.maps.Size). Kích thước phải là hình chữ nhật, mặc dù không nhất thiết phải là hình vuông.
  • getTileUrl (bắt buộc) chỉ định hàm, thường được cung cấp dưới dạng giá trị cố định của hàm cùng dòng, để xử lý việc lựa chọn ô hình ảnh thích hợp dựa trên toạ độ thế giới và mức thu phóng được cung cấp.

Đoạn mã sau đây triển khai một ImageMapType cơ bản bằng cách sử dụng các ô mặt trăng của Google. Ví dụ này sử dụng hàm chuẩn hoá để đảm bảo rằng các ô lặp lại dọc theo trục x, nhưng không lặp lại dọc theo trục y của bản đồ.

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;
Xem ví dụ

Dự đoán

Trái Đất là một hình cầu ba chiều (gần đúng), trong khi bản đồ là một bề mặt phẳng hai chiều. Bản đồ mà bạn thấy trong API JavaScript của Maps, giống như mọi bản đồ phẳng của Trái Đất, là một phép chiếu của hình cầu đó lên một bề mặt phẳng. Nói một cách đơn giản nhất, phép chiếu có thể được định nghĩa là việc ánh xạ các giá trị vĩ độ/kinh độ thành toạ độ trên bản đồ của phép chiếu.

Phép chiếu trong API JavaScript của Maps phải triển khai giao diện Projection. Quá trình triển khai Projection không chỉ phải cung cấp mối liên kết từ hệ toạ độ này sang hệ toạ độ khác, mà còn phải cung cấp mối liên kết hai chiều. Tức là bạn phải xác định cách chuyển đổi từ toạ độ Trái Đất (LatLng đối tượng) sang hệ toạ độ thế giới của lớp Projection và từ hệ toạ độ thế giới trở lại toạ độ Trái Đất. Google Maps sử dụng phép chiếu Mercator để tạo bản đồ từ dữ liệu địa lý và chuyển đổi các sự kiện trên bản đồ thành toạ độ địa lý. Bạn có thể lấy phép chiếu này bằng cách gọi getProjection() trên Map (hoặc bất kỳ loại MapType cơ sở tiêu chuẩn nào). Đối với hầu hết các mục đích sử dụng, Projection tiêu chuẩn này là đủ, nhưng bạn cũng có thể xác định và sử dụng các phép chiếu tuỳ chỉnh của riêng mình.

Triển khai phép chiếu

Khi triển khai phép chiếu tuỳ chỉnh, bạn cần xác định một số điều sau:

  • Công thức để ánh xạ toạ độ vĩ độ và kinh độ vào mặt phẳng Descartes và công thức tương ứng để ánh xạ từ mặt phẳng Descartes sang toạ độ vĩ độ và kinh độ. (Giao diện Projection chỉ hỗ trợ các phép biến đổi thành toạ độ thẳng.)
  • Kích thước ô cơ sở. Tất cả các ô đều phải có hình chữ nhật.
  • "Kích thước thế giới" của bản đồ sử dụng tập hợp ô cơ sở ở mức thu phóng 0. Xin lưu ý rằng đối với bản đồ bao gồm một ô ở mức thu phóng 0, kích thước thế giới và kích thước ô cơ sở là giống hệt nhau.

Phép biến đổi toạ độ trong phép chiếu

Mỗi phép chiếu cung cấp hai phương thức chuyển đổi giữa hai hệ toạ độ này, cho phép bạn chuyển đổi giữa toạ độ địa lý và toạ độ thế giới:

  • Phương thức Projection.fromLatLngToPoint() chuyển đổi giá trị LatLng thành toạ độ thế giới. Phương thức này được dùng để định vị lớp phủ trên bản đồ (và để định vị chính bản đồ).
  • Phương thức Projection.fromPointToLatLng() chuyển đổi toạ độ thế giới thành giá trị LatLng. Phương thức này được dùng để chuyển đổi các sự kiện (chẳng hạn như lượt nhấp) xảy ra trên bản đồ thành toạ độ địa lý.

Google Maps giả định rằng các phép chiếu là dạng thẳng.

Nhìn chung, bạn có thể sử dụng phép chiếu cho hai trường hợp: để tạo bản đồ thế giới hoặc để tạo bản đồ của một khu vực địa phương. Trong trường hợp trước, bạn nên đảm bảo rằng phép chiếu của bạn cũng là phép chiếu thẳng và bình thường ở mọi kinh độ. Một số phép chiếu (đặc biệt là phép chiếu hình nón) có thể "bình thường cục bộ" (tức là điểm phía bắc) nhưng lệch khỏi hướng bắc thực; ví dụ: bản đồ được đặt càng xa so với một số kinh độ tham chiếu. Bạn có thể sử dụng phép chiếu như vậy trên thiết bị, nhưng hãy lưu ý rằng phép chiếu này nhất thiết phải không chính xác và lỗi biến đổi sẽ ngày càng rõ ràng hơn khi bạn càng lệch khỏi kinh độ tham chiếu.

Lựa chọn mảnh bản đồ trong phép chiếu

Phép chiếu không chỉ hữu ích trong việc xác định vị trí của các địa điểm hoặc lớp phủ, mà còn giúp định vị chính các ô bản đồ. Maps JavaScript API kết xuất bản đồ cơ sở bằng giao diện MapType. Giao diện này phải khai báo cả thuộc tính projection để xác định phép chiếu của bản đồ và phương thức getTile() để truy xuất các ô bản đồ dựa trên giá trị toạ độ ô vuông. Toạ độ ô dựa trên cả kích thước ô cơ bản (phải là hình chữ nhật) và "kích thước thế giới" của bản đồ, tức là kích thước pixel của thế giới bản đồ ở mức thu phóng 0. (Đối với bản đồ bao gồm một ô ở mức thu phóng 0, kích thước ô và kích thước thế giới là giống hệt nhau.)

Bạn xác định kích thước ô cơ sở trong thuộc tính tileSize của MapType. Bạn xác định kích thước thế giới một cách ngầm định trong các phương thức fromLatLngToPoint() và fromPointToLatLng() của phép chiếu.

Vì việc chọn hình ảnh phụ thuộc vào các giá trị được truyền này, nên bạn nên đặt tên cho hình ảnh có thể được chọn theo phương thức lập trình dựa trên các giá trị được truyền đó, chẳng hạn như map_zoom_tileX_tileY.png.

Ví dụ sau đây xác định một ImageMapType bằng cách sử dụng phép chiếu Gall-Peters:

TypeScript

// This example defines an image map type using the Gall-Peters
// projection.
// https://en.wikipedia.org/wiki/Gall%E2%80%93Peters_projection
const mapElement = document.querySelector('gmp-map')!;
let innerMap: google.maps.Map;

async function init() {
    // Request the needed libraries.
    await google.maps.importLibrary('maps');

    // Create a map.
    innerMap = mapElement.innerMap;
    innerMap.setOptions({
        mapTypeControl: false,
    });

    // Set the Gall-Peters map type.
    const gallPetersMapType = await initGallPeters();
    innerMap.mapTypes.set('gallPeters', gallPetersMapType);
    innerMap.setMapTypeId('gallPeters');

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

    innerMap.addListener('mousemove', (event: google.maps.MapMouseEvent) => {
        coordsDiv.textContent = `lat: ${String(Math.round(event.latLng!.lat()))}, lng: ${String(Math.round(event.latLng!.lng()))}`;
    });

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

async function initGallPeters(): Promise<google.maps.ImageMapType> {
    const [{ ImageMapType }, { Size, Point, LatLng }] = await Promise.all([
        google.maps.importLibrary('maps'),
        google.maps.importLibrary('core'),
    ]);

    const GALL_PETERS_RANGE_X = 800;
    const GALL_PETERS_RANGE_Y = 512;

    // Fetch Gall-Peters tiles stored locally on our server.
    const gallPetersMapType = new ImageMapType({
        getTileUrl(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 `gall-peters_${String(zoom)}_${String(x)}_${String(y)}.png`;
        },
        tileSize: new 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(latLng: google.maps.LatLng) {
            const latRadians = (latLng.lat() * Math.PI) / 180;
            return new Point(
                GALL_PETERS_RANGE_X * (0.5 + latLng.lng() / 360),
                GALL_PETERS_RANGE_Y * (0.5 - 0.5 * Math.sin(latRadians))
            );
        },
        fromPointToLatLng(point: google.maps.Point, noWrap?: boolean) {
            const x = point.x / GALL_PETERS_RANGE_X;
            const y = Math.max(0, Math.min(1, point.y / GALL_PETERS_RANGE_Y));

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

    return gallPetersMapType;
}

// 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' },
        },
    ],
};

void init();

JavaScript

// This example defines an image map type using the Gall-Peters
// projection.
// https://en.wikipedia.org/wiki/Gall%E2%80%93Peters_projection
const mapElement = document.querySelector('gmp-map');
let innerMap;

async function init() {
    // Request the needed libraries.
    await google.maps.importLibrary('maps');

    // Create a map.
    innerMap = mapElement.innerMap;
    innerMap.setOptions({
        mapTypeControl: false,
    });

    // Set the Gall-Peters map type.
    const gallPetersMapType = await initGallPeters();
    innerMap.mapTypes.set('gallPeters', gallPetersMapType);
    innerMap.setMapTypeId('gallPeters');

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

    innerMap.addListener('mousemove', (event) => {
        coordsDiv.textContent = `lat: ${String(Math.round(event.latLng.lat()))}, lng: ${String(Math.round(event.latLng.lng()))}`;
    });

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

async function initGallPeters() {
    const [{ ImageMapType }, { Size, Point, LatLng }] = await Promise.all([
        google.maps.importLibrary('maps'),
        google.maps.importLibrary('core'),
    ]);

    const GALL_PETERS_RANGE_X = 800;
    const GALL_PETERS_RANGE_Y = 512;

    // Fetch Gall-Peters tiles stored locally on our server.
    const gallPetersMapType = new ImageMapType({
        getTileUrl(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 `gall-peters_${String(zoom)}_${String(x)}_${String(y)}.png`;
        },
        tileSize: new 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(latLng) {
            const latRadians = (latLng.lat() * Math.PI) / 180;
            return new Point(
                GALL_PETERS_RANGE_X * (0.5 + latLng.lng() / 360),
                GALL_PETERS_RANGE_Y * (0.5 - 0.5 * Math.sin(latRadians))
            );
        },
        fromPointToLatLng(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 LatLng(
                (Math.asin(1 - 2 * y) * 180) / Math.PI,
                -180 + 360 * x,
                noWrap
            );
        },
    };

    return gallPetersMapType;
}

// 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' },
        },
    ],
};

void init();
Xem ví dụ