Tipos de mapa

Selecione a plataforma: Android iOS JavaScript

Neste documento, falamos sobre os tipos de mapa que podem ser mostrados usando a API Maps JavaScript. A API usa um objeto MapType para reter informações sobre esses mapas. A interface MapType define a exibição e o uso de blocos de mapa e a tradução de sistemas de coordenadas a partir de coordenadas de tela para coordenadas do mundo (no mapa). Cada MapType precisa conter alguns métodos para lidar com a recuperação e a liberação de blocos e propriedades que definem o comportamento visual.

O funcionamento interno dos tipos de mapas na API Maps JavaScript é um tópico avançado. A maioria dos desenvolvedores pode usar os tipos de mapa básicos observados abaixo. No entanto, você também pode modificar a apresentação de tipos de mapa existentes usando mapas estilizados ou definir seus próprios blocos de mapas usando tipos de mapa personalizados. Ao fornecer tipos de mapa personalizados, você precisará entender como modificar o Registro do tipo do mapa.

Tipos de mapa básicos

Há quatro tipos de mapa disponíveis na API Maps JavaScript. Além dos blocos de mapa de vias "pintados" conhecidos, a API Maps JavaScript também aceita outros tipos de mapa.

Os seguintes tipos de mapa estão disponíveis na API Maps JavaScript:

  • O roadmap mostra a visualização de mapa de vias padrão. Esse é o tipo de mapa padrão.
  • O satellite mostra imagens de satélite do Google Earth.
  • O hybrid mostra uma combinação de visualizações normal e de satélite.
  • terrain mostra um mapa físico com base nas informações do terreno.

Modifique o tipo de mapa em uso pelo Map definindo sua propriedade mapTypeId, seja dentro do construtor definindo seu objeto Map options ou chamando o método setMapTypeId() do mapa. A propriedade mapTypeID assume o roadmap como padrão.

Como definir o mapTypeId na construção:

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

Modifique o mapTypeId dinamicamente:

map.setMapTypeId('terrain');

O tipo de mapa não é definido diretamente. Na verdade, você define o mapTypeId para que se refira a um MapType usando um identificador. A API Maps JavaScript usa um registro de tipo de mapa, explicado abaixo, para gerenciar essas referências.

Imagens em 45°

A API Maps JavaScript é compatível com imagens especiais em 45° para determinados locais. Essas imagens de alta resolução oferecem visualizações de perspectiva na direção de cada ponto cardeal (norte, sul, leste e oeste). Essas imagens estão disponíveis em níveis de resolução mais altos para os tipos de mapa permitidos.

A imagem a seguir mostra uma vista em perspectiva de 45° da cidade de Nova York:

Os tipos de mapa satellite e hybrid são compatíveis com imagens em 45° em níveis altos de zoom (12 ou mais), quando disponíveis. Se o usuário aumenta o zoom em uma localização onde existem essas imagens, esses tipos de mapa mudam automaticamente a visualização da seguinte forma:

  • As imagens de satélite ou híbridas são substituídas por imagens com uma perspectiva de 45° centralizada na localização atual. Por padrão, essas visualizações são orientadas para o norte. Se o usuário reduzir o zoom, as imagens de satélite ou híbridas padrão aparecerão novamente. O comportamento varia de acordo com o nível de zoom e o valor de tilt:
    • Entre os níveis de zoom 12 e 18, o mapa base de cima para baixo (0°) é mostrado por padrão, a menos que tilt esteja definido como 45.
    • Em níveis de zoom de 18 ou maiores, o mapa base em 45° é mostrado, a menos que tilt seja definido como 0.
  • O controle de rotação fica visível. O controle de rotação oferece opções que permitem ao usuário alternar inclinação e girar a visualização em incrementos de 90° em qualquer direção. Para ocultar o controle de rotação, defina rotateControl como false.

A redução do zoom de um tipo de mapa que exibe imagens em 45° reverte todas essas alterações, restabelecendo os tipos de mapa originais.

Ativar e desativar imagens em 45°

Para desativar as imagens em 45°, chame setTilt(0) no objeto Map. Para ativar imagens em 45° para os tipos de mapa compatíveis, chame setTilt(45). O método getTilt() de Map reflete sempre o tilt atual que está sendo mostrado no mapa. Se você definir um tilt em um mapa e depois remover esse tilt (diminuindo o zoom do mapa, por exemplo), o método getTilt() vai retornar 0.

Importante: imagens em 45° só são compatíveis com mapas de varredura, e não podem ser usadas com mapas vetoriais.

O exemplo a seguir mostra uma vista de 45° da cidade de Nova York:

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

Testar amostra

Exemplo

Girar imagens em 45°

Na verdade, as imagens em 45° consistem em uma coleção de imagens para cada ponto cardeal (norte, sul, leste e oeste). Quando seu mapa está mostrando uma imagem em 45°, ela pode ser orientada para um dos pontos cardeais chamando setHeading() no objeto Map e transmitindo um valor numérico expresso em graus em relação ao Norte.

O exemplo a seguir mostra um mapa aéreo e faz a rotação automática dele a cada três segundos quando o botão é clicado:

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

Testar amostra

Exemplo

Modificar o registro de tipo de mapa

O mapTypeId do mapa é um identificador de string usado para associar um MapType a um valor exclusivo. Cada objeto Map mantém um MapTypeRegistry contendo a coleção de MapTypes disponíveis para esse mapa. Por exemplo, esse registro é usado para selecionar os tipos de mapas disponíveis no controle MapType do mapa.

O registro de tipos de mapa não é lido diretamente. Em vez disso, modifique o registro adicionando tipos personalizados e associando esses mapas a um identificador de string de sua escolha. Não é possível mudar os tipos de mapa básicos, mas eles podem ser removidos, mudando a aparência do mapTypeControlOptions associado ao mapa.

O código a seguir serve para mostrar só dois tipos no mapTypeControlOptions do mapa e modifica o registro, adicionando a associação com este identificador para a implementação efetiva da interface 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);

Mapas estilizados

Com o StyledMapType, você personaliza a apresentação dos mapas base padrão do Google, mudando a exibição visual de elementos, como estradas, parques e áreas construídas para refletir um estilo diferente daquele usado no tipo de mapa padrão.

Para mais informações sobre o StyledMapType, consulte o guia sobre mapas estilizados.

Tipos de mapa personalizados

A API Maps JavaScript é compatível com a exibição e o gerenciamento de tipos de mapa personalizados. Assim, você pode implementar suas próprias imagens de mapa ou sobreposições de blocos.

Existem várias implementações de tipo de mapa possíveis na API Maps JavaScript:

  • Conjuntos de blocos padrão compostos por imagens que, coletivamente, formam os mapas cartográficos na íntegra. Esses conjuntos de blocos também são conhecidos como tipos de mapa básicos. Esses tipos de mapa agem e se comportam como os tipos de mapa padrão existentes: roadmap, satellite, hybrid e terrain. Você pode adicionar o tipo de mapa personalizado à matriz mapTypes de um mapa para permitir que a IU na API Maps JavaScript trate esse mapa como padrão e faça a inclusão dele no controle de horário de destino, por exemplo.
  • Sobreposições de bloco de imagens, que aparecem sobre os tipos de mapa base existentes. Geralmente, esses tipos de mapa são usados para aprimorar um tipo de mapa existente com a exibição de informações adicionais. Muitas vezes, são restritos a localizações e/ou níveis de zoom específicos. Esses blocos podem ser transparentes, permitindo adicionar recursos a mapas existentes.
  • Tipos de mapa sem imagens, que permitem manipular a exibição de informações do mapa no nível mais básico.

Cada uma dessas opções depende da criação de uma classe que implementa a interface MapTypeMapType. Além disso, a classe ImageMapType fornece alguns comportamentos integrados para simplificar a criação de tipos de mapas de imagens.

Interface do MapType

Antes de criar classes que implementam o MapType, é importante entender como o Google Maps determina as coordenadas e decide quais partes do mapa vão aparecer. Você precisa implementar uma lógica semelhante para todos os tipos de mapa base ou de sobreposição. Leia o guia sobre coordenadas de mapa e bloco.

Os tipos de mapa personalizados precisam implementar a interface MapType. Essa interface especifica determinadas propriedades e métodos que permitem que a API inicialize solicitações para os seus tipos de mapa quando determina que é necessário mostrar blocos de mapa dentro da porta de visualização e do nível de zoom atuais. Essas solicitações são processadas para decidir o bloco a ser carregado.

Observação: você pode criar sua própria classe para implementar essa interface. Como alternativa, se você tem imagens compatíveis, é possível usar a classe ImageMapType, que já implementa essa interface.

As classes que implementam a interface MapType exigem que você defina e preencha as seguintes propriedades:

  • A tileSize (obrigatória) especifica o tamanho do bloco (do tipo google.maps.Size). Os tamanhos precisam ser retangulares, mas não é necessário que sejam quadrados.
  • A maxZoom (obrigatória) especifica o nível máximo de zoom com que os blocos desse tipo de mapa deverão ser mostrados.
  • A minZoom (opcional) especifica o nível mínimo de zoom com que os blocos desse tipo de mapa deverão ser mostrados. Por padrão, esse valor é 0, indicando que não existem níveis mínimos de zoom.
  • name (opcional) especifica o nome desse tipo de mapa. Essa propriedade só é necessária quando esse tipo de mapa é selecionável em um controle MapType. Consulte Adicionar controles MapType abaixo.
  • alt (opcional) especifica o texto alternativo para esse tipo de mapa, que aparece como um texto flutuante. Essa propriedade só é necessária quando esse tipo de mapa é selecionável em um controle MapType. Consulte Adicionar controles MapType abaixo.

Além disso, as classes que implementam a interface MapType precisam implementar os seguintes métodos:

  • O getTile() (obrigatório) é chamado sempre que a API determina que o mapa precisa exibir novos blocos para a área de visualização especificada. O método getTile() precisa ter a seguinte assinatura:

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

    A API determina se ele precisa chamar getTile() com base nas propriedades tileSize, minZoom e maxZoom de MapType e na janela de visualização atual e nível de zoom do mapa. O manipulador desse método precisa retornar um elemento HTML com base em uma coordenada transmitida, um nível de zoom e um elemento DOM ao qual deve ser anexada a imagem do bloco.

  • releaseTile() (opcional) é chamado sempre que a API determina que o mapa precisa remover um bloco que saiu da área de visualização. Esse método precisa ter a seguinte assinatura:

    releaseTile(tile:Node)

    Normalmente, você é responsável por remover todos os elementos anexados a blocos de mapa na adição ao mapa. Por exemplo, se você anexou ouvintes de eventos às sobreposições de blocos do mapa, eles precisam ser removidos daqui.

O método getTile() age como o principal controlador para determinar quais blocos devem ser carregados dentro de uma área de visualização específica.

Tipos de mapa básicos

Os tipos de mapa construídos dessa forma podem ser independentes ou combinados com outros tipos de mapa como sobreposições. Os tipos de mapa autônomos são conhecidos como tipos de mapa base. A API deve tratar esses MapTypes personalizados da mesma forma que qualquer outro tipo de mapa base existente (ROADMAP, TERRAIN etc.). Para fazer isso, adicione o MapType personalizado à propriedade mapTypes do Map. Essa propriedade é do tipo MapTypeRegistry.

O código abaixo cria um MapType básico para exibir as coordenadas de bloco de um mapa e desenha um contorno dos blocos:

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

Testar amostra

Tipos de mapa de sobreposição

Alguns tipos de mapa são projetados para funcionar sobre tipos de mapa existentes. Esses tipos podem ter camadas transparentes indicando pontos de interesse ou mostrando outros dados ao usuário.

Nesses casos, não convém que o tipo de mapa seja tratado como uma entidade separada, mas como uma sobreposição. Para fazer isso, adicione o tipo de mapa diretamente a um MapType usando a propriedade overlayMapTypes do Map. Essa propriedade contém MVCArray de MapTypes. Todos os tipos de mapa (base e de sobreposição) são renderizados na camada mapPane. Os tipos de mapa de sobreposição aparecem sobre o mapa base em que estão anexados, na ordem em que aparecem na matriz Map.overlayMapTypes. As sobreposições com valores de índice mais altos aparecem na frente das sobreposições com valores de índice menores.

O exemplo abaixo é idêntico ao anterior. A única diferença é que criamos um MapType de sobreposição de bloco sobre o tipo de mapa 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;
Exemplo

Testar amostra

Tipos de mapa de imagem

A implementação de um MapType para agir como um tipo de mapa base pode ser uma tarefa árdua e demorada. A API fornece uma classe especial que implementa a interface MapType para os tipos de mapa mais comuns: tipos compostos de blocos formados por arquivos de imagem única.

Essa classe, a ImageMapType, é construída usando uma especificação do objeto ImageMapTypeOptions que define as seguintes propriedades obrigatórias:

  • A tileSize (obrigatória) especifica o tamanho do bloco (do tipo google.maps.Size). Os tamanhos precisam ser retangulares, mas não é necessário que sejam quadrados.
  • A getTileUrl (obrigatória) especifica a função, normalmente fornecida como uma função literal incorporada, para permitir a seleção do bloco de imagem apropriado com base nas coordenadas mundiais e no nível de zoom fornecidos.

O código a seguir implementa um ImageMapType básico usando blocos de lua do Google. O exemplo usa uma função de normalização para garantir que os blocos sejam repetidos ao longo do eixo x do mapa, mas não ao longo do eixo 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;
Exemplo

Testar amostra

Projeções

A Terra é uma esfera tridimensional (aproximadamente), e o mapa é uma superfície bidimensional plana. O mapa que você vê dentro da API Maps JavaScript, como qualquer mapa plano da Terra, é uma projeção dessa esfera em uma superfície plana. Nos termos mais simples, uma projeção pode ser definida como o mapeamento de valores de latitude/longitude em coordenadas no mapa da projeção.

As projeções na API Maps JavaScript precisam implementar a interface Projection. Uma implementação Projection precisa fornecer um mapeamento de um sistema de coordenadas para outro e um mapeamento bidirecional. Ou seja, você precisa definir como traduzir as coordenadas da Terra (objetos LatLng) para o sistema de coordenadas mundiais da classe Projection e vice-versa. O Google Maps usa a projeção Mercator para criar mapas de dados geográficos e converter eventos no mapa em coordenadas geográficas. Você pode conseguir essa projeção chamando getProjection() no Map (ou qualquer um dos tipos base de MapType padrão). Para a maioria dos usos, a Projection padrão é suficiente, mas você também pode definir e usar projeções personalizadas.

Implementar uma projeção

Ao implementar uma projeção personalizada, algumas definições são necessárias:

  • As fórmulas para mapear coordenadas de latitude e longitude em um plano cartesiano e vice-versa. A interface Projection só é compatível com transformações em coordenadas retilíneas.
  • O tamanho de bloco básico. Todos os blocos precisam ser retangulares.
  • O "tamanho do mundo" de um mapa usando o bloco base é definido como zoom no nível 0. Em mapas que consistem em um bloco com zoom 0, o tamanho do mundo e o tamanho do bloco base são idênticos.

Transformações de coordenadas em projeções

Cada projeção oferece dois métodos de conversão entre os dois sistemas de coordenadas, geográficas e mundiais:

  • O método Projection.fromLatLngToPoint() converte um valor de LatLng em uma coordenada mundial. Esse método é usado para posicionar sobreposições no mapa (e para posicionar o próprio mapa).
  • O método Projection.fromPointToLatLng() converte uma coordenada mundial em um valor LatLng. Esse método é usado para converter eventos ocorridos no mapa, como cliques, em coordenadas geográficas.

O Google Maps assume que as projeções são retilíneas.

Geralmente, a projeção é usada em dois casos: para criar um mapa do mundo ou de uma área local. No primeiro caso, é necessário garantir que a projeção seja retilínea e normal em todas as longitudes. Algumas projeções (especialmente projeções cônicas) podem ser "localmente normais" (ou seja, apontar para o Norte), mas se desviarem do Norte absoluto. Por exemplo, a distância da posição do mapa com relação a algumas longitudes de referência. Essa projeção pode ser usada localmente. No entanto, ela será necessariamente imprecisa, e os erros de transformação vão ficar cada vez mais evidentes à medida que a projeção se afastar da longitude de referência.

Seleção de blocos de mapa em projeções

Além de determinar as posições de localizações ou sobreposições, as projeções também são úteis para posicionar os próprios blocos de mapa. A API Maps JavaScript renderiza mapas base buscando uma interface MapType, que precisa declarar uma propriedade projection para identificar a projeção do mapa e um método getTile() para recuperar blocos de mapa com base nos valores de coordenadas de bloco. As coordenadas de bloco são baseadas no tamanho básico do seu bloco (que deve ser retangular) e no "tamanho do mundo" do seu mapa, que é o tamanho do pixel do mapa mundial com zoom no nível 0. Para mapas compostos por um bloco em zoom 0, o tamanho do mundo e o tamanho do bloco são idênticos.

Defina o tamanho da base do bloco dentro da propriedade tileSize de MapType. Defina o tamanho do mundo implicitamente dentro dos métodos fromLatLngToPoint() e fromPointToLatLng() da projeção.

Como a seleção da imagem depende desses valores transmitidos, é útil nomear as imagens que podem ser selecionadas de forma programática com base nos valores passados, como map_zoom_tileX_tileY.png.

O exemplo a seguir define um ImageMapType usando a projeção Gall-Peters:

TypeScript

// This example defines an image map type using the Gall-Peters
// projection.
// https://en.wikipedia.org/wiki/Gall%E2%80%93Peters_projection

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

Testar amostra