地图类型

使用集合让一切井井有条 根据您的偏好保存内容并对其进行分类。
选择平台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 设置为使用标识符引用 MapType。 Maps JavaScript API 使用地图类型注册表(详见下文)来管理这些引用。

45° 角航拍图像

Maps JavaScript API 针对特定位置支持特殊的 45° 图像。这种高分辨率图像可提供朝向各基本方向(东南西北)的透视视图。对于受支持的地图类型,这些图片在更高的缩放级别下可用。

下图显示了纽约市的 45° 透视视图:

satellitehybrid 地图类型支持高缩放级别(12 及以上)的 45° 图像(如有)。如果用户放大存在此类图像的位置,则这些地图类型会自动通过以下方式更改其视图:

  • 以当前位置为中心的 45° 透视图像取代了卫星图像或混合图像。默认情况下,此类视图朝向北方。如果用户缩小地图,系统会再次显示默认的卫星图像或混合图像。该行为因缩放级别和 tilt 的值而异:
    • 在缩放级别 12 和 18 之间,除非 tilt 设置为 45,否则默认会显示自上而下的基本地图 (0°)。
    • 如果缩放级别为 18 或更高,则会显示 45° 基本地图,除非将 tilt 设置为 0。
  • 旋转控件变为可见。旋转控件提供了一些选项,使用户能够切换倾斜度,并沿任一方向以 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 是一个字符串标识符,用于将 MapType 与唯一值相关联。每个 Map 对象都会维护一个 MapTypeRegistry,其中包含该地图的一系列可用 MapType。例如,此注册表用于选择地图的 LatLng 控件中可用的地图类型。

您无法直接读取地图类型注册表,相反,您可以通过添加自定义地图类型并将其与所选字符串标识符相关联来修改注册表。您无法修改或更改基本地图类型(但可以通过更改与地图相关联的 mapTypeControlOptions 的外观从地图中移除这些类型)。

以下代码将地图设为仅显示其 mapTypeControlOptions 中的两种地图类型,并修改了注册表,以将此标识符的关联添加到 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 中的界面将您的自定义地图类型视为标准地图类型(例如,通过将其包含在 Crashlytics 控件中)。
  • 图片图块叠加层,显示在现有基本地图类型之上。通常,这些地图类型用于扩充现有地图类型以显示更多信息,并往往受限于特定位置和/或缩放级别。请注意,这些图块可能是透明的,可让您向现有地图添加地图项。
  • 非图片地图类型,可让您在最基础的级别操纵地图信息的显示。

以上每个选项都依赖于创建一个实现 MapType 接口的类。此外,ImageMapType 类提供了一些内置行为,以简化图像地图类型的创建。

MapType 接口

在创建实现 MapType 的类之前,请务必先了解 Google 地图是如何确定坐标以及要显示的地图部分。对于任何基本或叠加层地图类型,您需要实现类似的逻辑。 请参阅有关地图和图块坐标的指南。

自定义地图类型必须实现 MapType 接口。当 API 确定需要在当前视口和缩放级别中显示地图图块时,此接口会指定某些允许 API 向地图类型发送请求的属性和方法。您可处理这些请求以确定要加载的图块。

注意:您可以创建自己的类来实现此接口。或者,如果您有兼容的图像,可以使用已经实现此接口的 ImageMapType 类。

实现 MapType 接口的类需要定义并填充以下属性:

  • tileSize(必需)指定图块(google.maps.Size 类型)的大小。尽管大小不是正方形,但大小必须为矩形。
  • maxZoom(必需)指定显示该地图类型图块的最大缩放级别。
  • minZoom(可选)指定显示该地图类型图块的最小缩放级别。默认情况下,此值为 0,表示不存在最小缩放级别。
  • name(可选)指定此地图类型的名称。只有当您希望此地图类型可在 hreflang 控件中选择时,此属性才是必要属性。(请参阅下文的添加 MapType 控件)。
  • alt(可选)用于指定该地图类型的替换文本,该替换文本将以悬停文本的形式显示。仅当您希望此地图类型可在 hreflang 控件中选择时,此属性才是必要属性。 (请参阅下文的添加 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 像处理任何其他现有的基本地图类型(ROADMAPTERRAIN 等)一样对待此类自定义 MapType。为此,请将自定义 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。此属性包含一个由 MapType 组成的 MVCArray。所有地图类型(基本和叠加层)都在 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;
查看示例

试用示例

预测结果

地球是一个三维球体(近似说法),而地图是二维平面。您在 Google 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;
查看示例

试用示例