Типы карт

Выберите платформу: Android iOS JavaScript

В этой статье описаны типы карт, которые можно отображать с помощью Maps JavaScript API. API использует объект MapType для хранения информации о картах. MapType представляет собой интерфейс, который определяет показ и использование фрагментов карты и перевод координат из экранных в мировые. Каждый MapType должен содержать ряд методов для обработки получения и показа фрагментов, а также свойства, определяющие их визуальные характеристики.

Настройка новых типов карт в Maps JavaScript API – задача не из простых. Поэтому большинство разработчиков используют стандартные типы карт, описанные ниже. Вы можете изменить внешний вид этих типов с помощью стилизованных карт или задать свои листы карты с помощью собственных типов. Для создания последних требуется освоить способ изменения реестра типов карт.

Базовые типы карт

В Maps JavaScript API доступно четыре типа карт, которые наверняка известны вам по приложению для пользователей.

Это следующие базовые типы карт:

  • roadmap – стандартная дорожная карта. Этот тип карты используется по умолчанию.
  • satellite – спутниковые снимки Google Earth.
  • 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° (доступны в некоторых регионах). Эти фотографии высокого разрешения позволяют увидеть трехмерное изображение поверхности Земли с наклоном на север, юг, восток, запад. Они доступны, когда поддерживаемые типы карты просматриваются в крупном масштабе.

В примере ниже показаны снимки Нью-Йорка.

Типы карт satellite и hybrid поддерживают снимки под углом 45° при показе в крупном масштабе (уровень 12 и выше). Если пользователь увеличивает масштаб области, для которой доступны такие снимки, вид карт автоматически меняется следующим образом:

  • Спутниковые или гибридные изображения заменяются изображениями под углом 45°, отцентрированными по текущему местоположению. По умолчанию такие изображения ориентированы на север. Если пользователь уменьшит масштаб, на карте снова появятся спутниковые или гибридные изображения по умолчанию, в зависимости от уровня масштабирования и значения tilt:
    • На уровнях 12–18 по умолчанию показывается стандартная карта (проекция 0°), только если для tilt не задано значение 45.
    • На уровнях 18 и выше показываются снимки под углом 45°, только если для кода tilt не задано значение 0.
  • При этом на карте появляется элемент управления ориентацией. С его помощью пользователь может включать/отключать наклон, менять ориентацию карты в любом направлении с шагом 90°. Чтобы скрыть этот элемент, установите для rotateControl значение false.

При уменьшении масштаба карты с изображением под углом 45° все изменения сбрасываются – и восстанавливается первоначальный тип карты.

Включение и отключение снимков под углом 45°

Отключить снимки под углом 45° можно, вызвав setTilt(0) в объекте Map. Чтобы включить их в поддерживаемых типах карт, вызовите setTilt(45). Метод getTilt() объекта Map всегда отражает текущее значение 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° на карте можно сориентировать в любую из сторон света, вызвав метод setHeading() объекта Map и задав отклонение от направления на север в градусах.

Код в примере ниже вызывает карту, составленную по аэрофотоснимкам, которая автоматически поворачивается каждые 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;
Посмотреть пример

Примеры кода

Посмотреть пример.

Изменение реестра MapType

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 предусмотрено несколько возможных реализаций типа карт.

  • Стандартные наборы фрагментов, содержащие снимки, совокупность которых формирует полные географические карты. Эти наборы фрагментов известны также как базовые типы карт. Данные типы карт ведут себя аналогично типам карт по умолчанию: roadmap, satellite, hybrid и terrain. Вы можете добавить собственный тип в массив mapTypes карты, чтобы интерфейс Maps JavaScript API мог интерпретировать ваш тип карты как стандартный (например, включив его в элемент управления MapType).
  • Наложения поверх базовых типов карты. Обычно они дополняют существующие типы и используются для отображения дополнительной информации. Использование наложений часто ограничено определенными местами и/или масштабами карты. Наложения могут быть прозрачными, за счет чего элементы в них как бы добавляются прямо на существующую карту.
  • Неграфические типы карт, позволяющие изменять отображение информации на самом базовом уровне карты.

В основе этих возможностей лежит создание класса, реализующего интерфейс MapType. Кроме этого, класс ImageMapType предоставляет ряд встроенных функций, упрощающих создание типов карт на основе снимков.

Интерфейс MapType

Прежде чем создавать классы, реализующие интерфейс MapType, важно понять, как Google Карты определяют координаты и решают, какую часть карты показывать. Вам потребуется реализовать эту логику в любых типах базовых карт и наложений. Подробные сведения приведены в статье Координаты карт и фрагментов.

При работе с пользовательскими типами карт следует реализовать интерфейс MapType. Этот интерфейс задает определенные свойства и методы, позволяющие API отправлять запросы к вашим типам карт, когда API требуется отобразить листы карты для текущей области просмотра и масштаба. Ваше приложение обрабатывает эти запросы и определяет, какой лист нужно загрузить.

Примечание. Вы можете создать собственный класс, реализующий этот интерфейс. Если же у вас есть совместимые снимки, используйте класс ImageMapType, в котором интерфейс уже реализован.

При работе с классами, реализующими интерфейс MapType, необходимо определить следующие свойства и задать их значения:

  • tileSize (обязательно) – определяет размер фрагмента типа google.maps.Size (должен быть прямоугольным, но необязательно квадратным).
  • maxZoom (обязательно) – определяет максимальный уровень масштабирования, при котором следует показывать фрагменты данного типа карты.
  • minZoom (необязательно) – определяет минимальный уровень масштабирования, при котором следует показывать фрагменты данного типа карты. По умолчанию данное значение равно 0. Это значит, что минимального уровня масштабирования не существует.
  • name (необязательно) – определяет имя данного типа карты. Это свойство необходимо только в том случае, если вы хотите, чтобы этот тип карт можно было выбирать в элементе управления MapType (см. раздел Добавление элементов управления MapType ниже).
  • alt (необязательно) – определяет альтернативный текст для данного типа карты, отображаемый при наведении курсора мыши. Это свойство необходимо, только если вы хотите, чтобы этот тип карты можно было выбирать с помощью элемента управления MapType (cм. раздел Добавление элементов управления MapType ниже).

Также в классах, реализующих интерфейс MapType, необходимо реализовать следующие методы:

  • getTile() (обязательно) – вызывается, когда API определяет, что на карте необходимо отобразить новые фрагменты для заданного окна просмотра. Метод getTile() должен иметь следующий вид:

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

    API определяет, нужно ли вызвать getTile(), на основе имеющихся в MapType свойств tileSize, minZoom и maxZoom, а также текущего окна просмотра карты и коэффициента масштабирования. Обработчик этого метода должен возвращать элемент HTML с учетом переданных координат, уровня масштабирования и элемента DOM, к которому нужно прикрепить лист.

  • releaseTile() (необязательно) – вызывается, когда API определяет, что с карты необходимо удалить фрагмент, выходящий за границы. Этот метод имеет следующий синтаксис:

    releaseTile(tile:Node)

    Ваше приложение должно обрабатывать удаление всех элементов, прикрепленных к добавленным на карту листам. Например, если вы прикрепили блоки прослушивания событий к наложениям с листами карты, их нужно удалить с помощью этого метода.

Метод getTile() выполняет функции главного контроллера, определяющего, какие фрагменты необходимо загрузить для данного окна просмотра.

Базовые типы карт

Типы карт, создаваемые таким способом, могут быть отдельными или сочетаться с другими типами карт как наложения. Отдельные типы карт называются базовыми. Иногда требуется, чтобы API обрабатывал эти персонализированные типы MapType таким же способом, как и любые другие из существующих базовых типов (ROADMAP, TERRAIN и т. д.). Для этого необходимо добавить пользовательский MapType к свойству mapTypes объекта Map. Это свойство принадлежит типу 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;
Посмотреть пример

Примеры кода

Типы карт-наложений

Некоторые карты создаются как наложения поверх карт базовых типов. Это могут быть прозрачные наложения с нанесенными поверх них объектами и информацией для пользователей.

С такими наложениями не нужно обращаться как с отдельной сущностью: их лучше добавить непосредственно к существующему объекту MapType при помощи свойства Map объекта overlayMapTypes. Это свойство содержит массив MVCArray значений MapType. Все типы карт (базовые и наложения) отображаются в слое mapPane. Наложения отображаются поверх базовых карт, к которым они прикреплены, в том же порядке, в каком они перечислены в массиве Map.overlayMapTypes (чем выше у наложения значение индекса, тем выше находится его слой).

Следующий пример аналогичен предыдущему, за исключением наложения фрагмента MapType поверх карты типа 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;
Посмотреть пример

Примеры кода

Типы карт на основе снимков

Реализация базового типа карты MapType требует значительных затрат времени и усилий. В API предусмотрен специальный класс, реализующий интерфейс MapType для карт наиболее распространенного типа – состоящих из набора фрагментов в виде отдельных графических файлов.

Этот класс (ImageMapType) создается при помощи спецификации объекта ImageMapTypeOptions, определяющей следующие обязательные свойства:

  • tileSize (обязательно) – определяет размер фрагмента типа google.maps.Size (должен быть прямоугольным, но необязательно квадратным).
  • getTileUrl (обязательно) – задает функцию, обычно встроенный литерал, для выбора соответствующего фрагмента в зависимости от переданных мировых координат и уровня масштабирования.

В следующем примере базовый объект ImageMapType реализует фрагменты со снимками лунной поверхности от Google. Задействована функция нормализации для повторения листов по оси 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 Maps API использует проекцию Меркатора для создания карт на основе географических данных и преобразования событий карты в географические координаты. Чтобы получить эту проекцию, следует вызвать метод getProjection() для объекта Map (или любого другого базового типа MapType). Хотя стандартного класса Projection достаточно для большинства задач, вы можете определять и использовать персонализированные проекции.

Реализация проекции

При реализации собственной проекции вам потребуется определить:

  • Формулу привязки координат широты и долготы к картезианской плоскости и наоборот (интерфейс Projection поддерживает только преобразование в прямолинейные координаты).
  • Базовый размер листа карты. Все фрагменты должны быть прямоугольными.
  • "Размер мира", отображаемого на карте. Для этого используется набор базовых фрагментов при уровне масштабирования 0. Для карт, состоящих из одного фрагмента при уровне масштабирования 0, "размер мира" и размер базового фрагмента одинаковы.

Преобразования координат в проекциях

Каждая проекция предоставляет два метода для преобразования географических координат в мировые и наоборот:

  • Метод Projection.fromLatLngToPoint() преобразует значение LatLng в мировую координату. Этот метод используется для позиционирования наложений на карте и самой карты.
  • Метод Projection.fromPointToLatLng() преобразует мировую координату в значение LatLng. Этот метод используется для преобразования событий на карте (например, нажатий мыши) в географические координаты.

В Google Картах все проекции прямоугольные.

Чаще всего проекции используются для двух случаев: создания карты мира и создания карты местности. В первом случае необходимо убедиться, что ваша проекция является прямоугольной и нормальной для всех значений долготы. Некоторые проекции, особенно конические, могут быть "локально нормализованными" (например, указывать на север), но отклоняться от направления на истинный север по мере удаления от некоторой опорной долготы. Вы можете использовать такую проекцию локально, но следует помнить, что она обязательно будет неточной (и ошибки преобразования будут всё более заметны при отклонении от эталонной долготы).

Выбор листа карты для проекции

Проекции применяются не только для определения положения мест и наложений, но и для позиционирования самих листов карты. Maps JavaScript API визуализирует базовые карты с помощью интерфейса MapType, в котором следует объявить свойство projection (для идентификации проекции карты) и метод getTile() (для извлечения фрагментов карты на основе их координат). Координаты фрагментов зависят и от стандартного размера фрагмента (должен быть прямоугольным), и от "размера мира" – размера мира на вашей карте в пикселях при уровне масштабирования 0. На картах, состоящих из одного листа с уровнем масштабирования 0, размер листа и размер мира совпадают.

Размер базового фрагмента задается в свойстве tileSize типа карты MapType. Размер мира задается явно в методах 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;
Посмотреть пример

Примеры кода