地图类型

请选择平台: 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');

请注意,您实际上并没有直接设置地图的地图类型,而是使用标识符设置其 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 是一种字符串标识符,用于将 MapType 与唯一值关联起来。每个 Map 对象都会保留一个 MapTypeRegistry,后者包含相应地图可用的 MapType 的集合。该注册表用于选择地图的类型,例如,地图 MapType 控件中可用的地图类型。

您不能直接从地图类型注册表中读取,而应修改注册表,只需添加自定义地图类型并将其与您选择的字符串标识符相关联即可。您无法修改或更改基本地图类型(但您可以通过更改与地图相关联的 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。您可以将自定义地图类型添加到地图的 mapTypes 数组中,以便 Maps JavaScript API 中的界面将自定义地图类型视为标准地图类型(例如,通过将自定义地图类型添加到 MapType 控件中)。
  • 图像图块叠加层,叠加在现有基本地图类型上显示。一般情况下,这些地图类型用于扩充现有地图类型以显示更多信息,并且往往受限于特定位置和/或缩放级别。请注意,这些图块可能是透明的,以便您向现有地图添加地图项。
  • 非图像地图类型,可让您在最基础的级别操纵地图信息的显示。

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

MapType 接口

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

自定义地图类型必须实现 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 像对待任何其他现有的基本地图类型(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 Moon 图块实现基本的 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;
查看示例

试用示例

投影

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

试用示例