Tipos de mapas

Seleccionar plataforma: Android iOS JavaScript

En este documento, se analizan los tipos de mapas que puedes mostrar con la API de Maps JavaScript. La API usa un objeto MapType para contener información sobre estos mapas. Un MapType es una interfaz que define la visualización y el uso de los mosaicos de mapas y la traducción de los sistemas de coordenadas, desde coordenadas en pantalla hasta coordenadas mundiales (en el mapa). Cada MapType debe contener algunos métodos para controlar la recuperación y liberación de mosaicos, y las propiedades que definen su comportamiento visual.

El funcionamiento interno de los tipos de mapas en la API de Maps JavaScript es un tema avanzado. La mayoría de los desarrolladores pueden usar los tipos de mapas básicos que se indican a continuación. Sin embargo, también puedes modificar la presentación de los tipos de mapas existentes mediante los mapas con diseños o definir tus propios mosaicos mediante los tipos de mapas personalizados. Cuando proporciones tipos de mapas personalizados, deberás comprender cómo modificar el registro de tipos de mapas.

Tipos de mapas básicos

Hay cuatro tipos de mapas disponibles en la API de Maps JavaScript. Además de los mosaicos de mapas de rutas "pintados", la API de Maps JavaScript también admite otros tipos de mapas.

Los siguientes tipos de mapas están disponibles en la API de Maps JavaScript:

  • roadmap muestra la vista del mapa de ruta predeterminado. Este es el tipo de mapa predeterminado.
  • satellite muestra imágenes satelitales de Google Earth.
  • hybrid muestra una combinación de vistas normales y satelitales.
  • terrain muestra un mapa físico basado en la información del terreno.

Para modificar el tipo de mapa que usa el Map, debes configurar su propiedad mapTypeId, ya sea dentro del constructor mediante la configuración de su objeto Map options o con una llamada al método setMapTypeId() del mapa. El valor predeterminado de la propiedad mapTypeID es roadmap.

Para configurar el mapTypeId durante la construcción, usa este código:

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

Para modificar el mapTypeId de forma dinámica, usa este código:

map.setMapTypeId('terrain');

Ten en cuenta que, en realidad, no configuras el tipo de mapa de forma directa, sino que configuras su mapTypeId para que haga referencia a un MapType mediante un identificador. La API de Maps JavaScript usa un registro de tipos de mapas, que se explica a continuación, para administrar estas referencias.

Imágenes a 45°

La API de Maps JavaScript admite imágenes especiales a 45° para determinadas ubicaciones. Las imágenes de alta resolución proporcionan vistas en perspectiva de cada punto cardinal (norte, sur, este y oeste). Estas imágenes se encuentran disponibles en niveles de zoom más altos para los tipos de mapas admitidos.

En la siguiente imagen, se muestra una vista de perspectiva a 45° de la ciudad de Nueva York:

Los tipos de mapa satellite y hybrid admiten imágenes a 45° en niveles de zoom altos (12 y superiores) cuando están disponibles. Si un usuario acerca una ubicación para la que existen estas imágenes, las vistas de estos tipos de mapas se modifican automáticamente de la siguiente manera:

  • Se reemplazan las imágenes satelitales o híbridas por imágenes que ofrecen una perspectiva de 45°, centradas en la ubicación actual. De manera predeterminada, esas vistas se orientan hacia el norte. Si el usuario aleja el mapa, las imágenes satelitales o híbridas predeterminadas aparecen nuevamente. El comportamiento varía según el nivel de zoom y el valor de tilt:
    • Entre los niveles de zoom 12 y 18, se muestra el mapa base de arriba abajo (0°) de forma predeterminada, a menos que la tilt se haya establecido en 45.
    • En los niveles de zoom 18 y superiores, se muestra el mapa base a 45°, a menos que la tilt se haya establecido en 0.
  • El control de rotación se vuelve visible. Este control ofrece opciones que permiten al usuario activar o desactivar la inclinación y rotar la vista en incrementos de 90° en cualquier dirección. Para ocultar el control de rotación, establece rotateControl en false.

Cuando el usuario aleja un tipo de mapa en el que se muestran imágenes a 45°, estos cambios se revierten y se restablecen los tipos de mapas originales.

Cómo habilitar e inhabilitar imágenes a 45°

Para inhabilitar las imágenes a 45°, llama a setTilt(0) en el objeto Map. A fin de habilitar las imágenes a 45° para los tipos de mapas admitidos, llama a setTilt(45). El método getTilt() de Map siempre reflejará la tilt actual que se muestra en el mapa. Si configuras una tilt en un mapa y, luego, quitas esa tilt (por ejemplo, al alejar el mapa), el método getTilt() del mapa mostrará 0.

Importante: Las imágenes a 45° solo son compatibles con los mapas de trama; estas imágenes no pueden usarse con mapas de vectores.

En el siguiente ejemplo, se muestra una vista a 45° de la ciudad de Nueva 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;
Ver ejemplo

Prueba la muestra

Ver ejemplo

Cómo rotar imágenes a 45°

Las imágenes a 45° son, en realidad, colecciones de imágenes correspondientes a cada punto cardinal (norte, sur, este, oeste). Una vez que el mapa muestre imágenes a 45°, puedes orientarlas hacia uno de sus puntos cardinales, Para eso, llama a setHeading() en el objeto Map y pásale un valor numérico expresado en grados con el norte como punto de partida.

En el siguiente ejemplo, se muestra un mapa aéreo que gira automáticamente cada 3 segundos cuando se hace clic en el botón:

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;
Ver ejemplo

Prueba la muestra

Ver ejemplo

Cómo modificar el registro de tipos de mapas

El mapTypeId de un mapa es un identificador de string que se usa para asociar un MapType con un valor único. Cada objeto Map mantiene un MapTypeRegistry que contiene la colección de MapType disponibles para ese mapa. Este registro se usa para seleccionar los tipos de mapas disponibles, por ejemplo, en el control MapType de Map.

Las lecturas no se hacen directamente desde el registro de tipos de mapas En lugar de ello, se modifica el registro y se agregan tipos de mapas personalizados que se asocian con un identificador de string que elijas. No se pueden modificar ni alterar los tipos de mapas básicos (aunque se pueden quitar del mapa si modificas el aspecto de las mapTypeControlOptions asociadas).

El siguiente código permite configurar el mapa para que se muestren únicamente dos tipos de mapas en las mapTypeControlOptions del mapa y modificar el registro para agregar la asociación con su identificador a la implementación real de la interfaz 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 con diseños

StyledMapType te permite personalizar la presentación de los mapas base estándares de Google y cambiar la visualización de elementos como rutas, parques y áreas con edificaciones para reflejar un diseño diferente del que se usa en el tipo de mapa predeterminado.

Para obtener más información sobre StyledMapType, consulta la guía sobre mapas con diseño.

Tipos de mapas personalizados

La API de Maps JavaScript admite la visualización y administración de tipos de mapas personalizados, lo que te permite implementar tus propias superposiciones de mosaicos o imágenes o de mapas.

Existen varias implementaciones posibles de tipos de mapas en la API de Maps JavaScript:

  • Conjuntos de mosaicos estándares que consisten en imágenes que en conjunto constituyen mapas cartográficos completos. Estos conjuntos de mosaicos también se conocen como tipos de mapas base. Estos tipos de mapas actúan y se comportan como aquellos existentes que son los predeterminados: roadmap, satellite, hybrid y terrain. Puedes agregar tu tipo de mapa personalizado a un array de mapTypes de Map para permitir que la IU de la API de Maps JavaScript trate tu tipo de mapa personalizado como un tipo de mapa estándar (si lo incluyes en el control MapType, por ejemplo).
  • Superposiciones de mosaicos de imágenes que se muestran sobre los tipos de mapas base existentes. Generalmente, estos tipos de mapas se usan para aumentar un tipo de mapa existente a fin de que se muestre información adicional y a menudo están limitados a ubicaciones o niveles de zoom específicos. Ten en cuenta que estos mosaicos pueden ser transparentes. Esto te permite agregar elementos a mapas existentes.
  • Tipos de mapas sin imágenes, que te permiten manipular la visualización de información en el nivel más esencial.

Cada una de estas opciones se basa en la creación de una clase que implementa la interfaz MapType. Además, la clase ImageMapType proporciona un comportamiento integrado para simplificar la creación de tipos de mapas de imágenes.

Interfaz de MapType

Antes de crear clases que implementen MapType, es importante comprender la forma en que Google Maps determina las coordenadas y decide qué partes del mapa mostrar. Debes implementar una lógica similar para cualquier tipo de mapa base o de superposición. Consulta la guía sobre coordenadas de mosaicos y mapas.

Los tipos de mapas personalizados deben implementar la interfaz MapType. Esta interfaz especifica ciertas propiedades y métodos que permiten a la API iniciar solicitudes para tus tipos de mapas cuando determina que debe mostrar mosaicos de mapas con el viewport y el nivel de zoom actuales. Debes controlar estas solicitudes para decidir qué mosaico se cargará.

Nota: Puedes crear tu propia clase para implementar esta interfaz. Como alternativa, si tienes imágenes compatibles, puedes usar la clase ImageMapType, que ya implementa esta interfaz.

Las clases que implementan la interfaz MapType requieren que definas y propagues las siguientes propiedades:

  • tileSize (obligatorio): Especifica el tamaño del mosaico (del tipo google.maps.Size). Debe tener forma rectangular, aunque no tiene que ser necesariamente cuadrado.
  • maxZoom (obligatorio): Especifica el nivel de zoom máximo en el que se deben mostrar los mosaicos de este tipo de mapa.
  • minZoom (opcional): Especifica el nivel de zoom mínimo en el que se deben mostrar los mosaicos de este tipo de mapa. De forma predeterminada, este valor es 0, lo que indica que no existe un nivel de zoom mínimo.
  • name (opcional): Especifica el nombre de este tipo de mapa. Esta propiedad solo es necesaria si deseas que se pueda seleccionar este tipo de mapa en un control MapType. (Consulta Cómo agregar controles MapType a continuación).
  • alt (opcional): Especifica el texto alternativo de este tipo de mapa, que se muestra cuando un usuario coloca el cursor sobre este. Esta propiedad solo es necesaria si deseas que se pueda seleccionar este tipo de mapa en un control MapType. (Consulta Cómo agregar controles MapType a continuación).

Además, las clases que implementan la interfaz MapType deben implementar los siguientes métodos:

  • Se llama a getTile() (obligatorio) cada vez que la API determina que el mapa debe mostrar mosaicos nuevos para un viewport determinado. El método getTile() debe tener la siguiente firma:

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

    La API determina si necesita llamar a getTile() según las propiedades tileSize, minZoom y maxZoom de MapType y el nivel de zoom y el viewport actuales del mapa. El controlador de este método debe mostrar un elemento HTML en función de una coordenada que se pasó, el nivel de zoom y el elemento del DOM al cual debe adjuntarse la imagen de mosaico.

  • Se llama a releaseTile() (opcional) cada vez que la API determina que el mapa debe quitar un mosaico, ya que queda fuera de la vista. El método debe tener la siguiente firma:

    releaseTile(tile:Node)

    Normalmente, debes quitar los elementos adjuntos a los mosaicos del mapa cuando los agregues al mapa. Por ejemplo, si adjuntaste objetos de escucha de eventos a superposiciones de mosaicos del mapa, debes quitarlos allí.

El método getTile() actúa como el controlador principal para determinar qué mosaicos cargar en un viewport determinado.

Tipos de mapas base

Los tipos de mapas que construyas de esta manera pueden ser independientes o combinarse con otros como superposiciones. Los tipos de mapas independientes se conocen como tipos de mapas base. Es recomendable que la API trate a los MapType personalizados como a cualquier otro tipo de mapa base existente (ROADMAP, TERRAIN, etc.). Para ello, agrega tu MapType personalizado a la propiedad mapTypes de Map. Esta propiedad es de tipo MapTypeRegistry.

El siguiente código crea un MapType base para mostrar las coordenadas de mosaicos de un mapa y dibuja un contorno de los mosaicos:

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;
Ver ejemplo

Prueba la muestra

Tipos de mapas de superposición

Algunos tipos de mapas están diseñados para funcionar encima de tipos de mapas existentes. Estos tipos de mapas tienen capas transparentes que muestran lugares de interés o datos adicionales a los usuarios.

En estos casos, no es recomendable que el tipo de mapa se trate como una entidad independiente, sino como una superposición. Para ello, agrega el tipo de mapa a un MapType existente directamente mediante la propiedad overlayMapTypes de Map. Esta propiedad contiene un MVCArray de MapType. Todos los tipos de mapas (base y de superposición) se renderizan en la capa mapPane. Los tipos de mapas de superposición aparecen encima del mapa base al que se adjuntan, en el orden en que aparecen en el array Map.overlayMapTypes (las superposiciones con valores de índices más altos se muestran encima de las superposiciones con valores de índices más bajos).

El siguiente ejemplo es idéntico al anterior, con la excepción de que creamos una superposición de mosaicos MapType sobre el 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;
Ver ejemplo

Prueba la muestra

Tipos de mapas de imágenes

La implementación de un MapType para que actúe como un tipo de mapa base puede demandar mucho tiempo y esfuerzo. La API proporciona una clase especial que implementa la interfaz MapType para los tipos de mapa más comunes: tipos de mapa que constan de mosaicos compuestos por archivos de una sola imagen.

Esta clase, la clase ImageMapType, se construye mediante una especificación del objeto ImageMapTypeOptions que define las siguientes propiedades obligatorias:

  • tileSize (obligatorio): Especifica el tamaño del mosaico (del tipo google.maps.Size). Debe tener forma rectangular, aunque no tiene que ser necesariamente cuadrado.
  • getTileUrl (obligatorio): Especifica la función, generalmente proporcionada como un literal de función intercalada, para controlar la selección del mosaico de imagen adecuado según las coordenadas mundiales y el nivel de zoom proporcionados.

En el siguiente código, se implementa un ImageMapType básico con los mosaicos de luna de Google. En el ejemplo, se emplea una función de normalización para garantizar que los mosaicos se repitan en el eje X (y no en el Y) de tu mapa.

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;
Ver ejemplo

Prueba la muestra

Proyecciones

La Tierra es una esfera tridimensional (aproximadamente), mientras que un mapa es una superficie bidimensional plana. El mapa que ves en la API de Maps JavaScript, como cualquier mapa plano de la Tierra, es una proyección de esa esfera en una superficie plana. En términos simples, una proyección se puede definir como una asignación de valores de latitud y longitud en las coordenadas del mapa de la proyección.

Las proyecciones de la API de Maps JavaScript deben implementar la interfaz Projection. Una implementación de Projection no solo debe proporcionar una asignación de un sistema de coordenadas a otro, sino también una asignación bidireccional. Es decir, debes definir cómo traducir las coordenadas terrestres (objetos LatLng) al sistema de coordenadas mundiales de la clase Projection, y viceversa. Google Maps usa la proyección Mercator para crear mapas a partir de datos geográficos y convertir eventos del mapa en coordenadas geográficas. Para obtener esta proyección, llama a getProjection() en el Map (o cualquiera de los tipos de MapType base estándares). Para la mayoría de los usos, esta Projection estándar será suficiente, pero también puedes definir y usar tus propias proyecciones personalizadas.

Cómo implementar una proyección

Al implementar una proyección personalizada, deberás definir algunos aspectos:

  • Las fórmulas para asignar coordenadas de latitud y longitud al plano cartesiano y viceversa. (La interfaz Projection solo admite transformaciones en coordenadas rectilíneas).
  • El tamaño de mosaico básico. Todos los mosaicos deben ser rectangulares.
  • El "tamaño del mundo" de un mapa con el conjunto de mosaicos básicos en el nivel de zoom 0. Ten en cuenta que, en el caso de los mapas compuestos por un mosaico en el nivel de zoom 0, el tamaño del mundo y el tamaño del mosaico básico son idénticos.

Cómo transformar coordenadas en proyecciones

Cada proyección proporciona dos métodos que permiten traducir de uno de estos dos sistemas de coordenadas al otro, de manera tal que puedes convertir coordenadas geográficas en mundiales y viceversa:

  • El método Projection.fromLatLngToPoint() convierte un valor LatLng en una coordenada mundial. Se usa para posicionar superposiciones en el mapa (y el mapa en sí).
  • El método Projection.fromPointToLatLng() convierte una coordenada mundial en un valor LatLng. Se usa para convertir eventos, como los clics en el mapa, en coordenadas geográficas.

Google Maps interpreta que las proyecciones son rectilíneas.

En general, puedes usar una proyección en dos casos: para crear un mapa del mundo o para crear un mapa de un área local. En el primer caso, debes asegurarte de que tu proyección también sea rectilínea y normal en todas las longitudes. Algunas proyecciones (en especial, las cónicas) pueden ser "normales a nivel local" (es decir, apuntar hacia el norte), pero desviarse del norte geográfico; por ejemplo, cuanto más lejos se posiciona el mapa respecto de una longitud de referencia determinada. Puedes usar esa proyección a nivel local, pero debes tener en cuenta que esta es necesariamente imprecisa y que los errores de transformación serán cada vez más notorios cuanta más desviación exista respecto de la longitud de referencia.

Selección de mosaicos de mapas en las proyecciones

Las proyecciones no solo son útiles para determinar las posiciones de ubicaciones o superposiciones, sino también para posicionar los propios mosaicos del mapa. La API de Maps JavaScript renderiza mapas base con una interfaz MapType, que debe declarar no solo una propiedad projection para identificar la proyección del mapa, sino también un método getTile() a fin de recuperar mosaicos de mapas en función de los valores de coordenadas de mosaicos. Las coordenadas de mosaicos se basan en el tamaño de tu mosaico básico (que debe ser rectangular) y el "tamaño del mundo" de tu mapa, que equivale al tamaño en píxeles de tu mapa del mundo en el nivel de zoom 0. Ten en cuenta que, en el caso de los mapas compuestos por un mosaico en el nivel de zoom 0, el tamaño del mundo y el tamaño de mosaico básico son idénticos.

Debes definir el tamaño del mosaico básico en la propiedad tileSize de tu MapType. Debes definir el tamaño del mundo de forma implícita en los métodos fromLatLngToPoint() y fromPointToLatLng() de tu proyección.

Dado que la selección de imágenes depende de los valores que se pasan, es útil nombrar imágenes que se puedan seleccionar de manera programática a partir de esos valores, como map_zoom_tileX_tileY.png.

En el siguiente ejemplo, se define un ImageMapType con la proyección 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;
Ver ejemplo

Prueba la muestra