Select platform: [Android](https://developers.google.com/maps/documentation/android-sdk/map.md.txt "View this page for the Android platform docs.") [iOS](https://developers.google.com/maps/documentation/ios-sdk/map.md.txt "View this page for the iOS platform docs.") [JavaScript](https://developers.google.com/maps/documentation/javascript/maptypes.md.txt "View this page for the JavaScript platform docs.")

This document discusses the types of maps you can display using the
Maps JavaScript API. The API uses a `MapType`
object to hold information about these maps. A `MapType`
is an interface that defines the display and usage of map tiles and
the translation of coordinate systems from screen coordinates to world
coordinates (on the map). Each `MapType` must contain a
few methods to handle retrieval and release of tiles, and properties
that define its visual behavior.

The inner workings of map types within the Maps JavaScript API
is an advanced topic. Most developers can use the [basic map types](https://developers.google.com/maps/documentation/javascript/maptypes.md.txt#BasicMapTypes) noted below. However, you can also modify the presentation of
existing map types using [Styled Maps](https://developers.google.com/maps/documentation/javascript/maptypes.md.txt#StyledMaps)
or define your own map tiles using [custom map types](https://developers.google.com/maps/documentation/javascript/maptypes.md.txt#CustomMapTypes).
When providing custom map types, you will need to understand how to modify the map's
[Map Type Registry](https://developers.google.com/maps/documentation/javascript/maptypes.md.txt#MapTypeRegistry).

## Basic Map Types

There are four types of maps available within the Maps JavaScript API.
In addition to the familiar "painted" road map tiles, the
Maps JavaScript API also supports other maps types.

The following map types are available in the Maps JavaScript API:

- `roadmap` displays the default road map view with basemap labels.
- `satellite` displays a photorealistic map based on aerial imagery.
- `hybrid` displays the satellite map view with basemap labels.
- `terrain` displays a physical map based on terrain information.

> [!NOTE]
> **Note:** 3D Maps only supports `satellite` and `hybrid` map types.

You modify the map type in use by the `Map` by setting its
`mapTypeId` property, either within the constructor by setting
its `Map options` object, or by calling the map's
`setMapTypeId()` method. The `mapTypeID` property
defaults to `roadmap`.

Setting the `mapTypeId` upon construction:

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

Modifying the `mapTypeId` dynamically:

```javascript
map.setMapTypeId('terrain');
```

Note that you don't actually set the map's map type directly,
but instead set its `mapTypeId` to reference a
`MapType` using an identifier.
The Maps JavaScript API uses a map type registry,
explained below, to manage these references.

### 3D Maps

The Maps JavaScript API supports 3D imagery. Many of the actions and
customization capabilities available for 2D maps are also available for 3D maps.

Learn more about 3D maps in the [3D maps overview](https://developers.google.com/maps/documentation/javascript/3d/overview.md.txt).

The following example shows a 3D perspective view of San Francisco:

<iframe src="https://developers.devsite.google/frame/maps/documentation/javascript/maptypes_40cdef998f9b1976a7ad6c5f8bd1e620a6eea02b646d85f52169a04468bb5e44.frame" class="framebox inherit-locale " allow="clipboard-write https://developers.devsite.google" allowfullscreen is-upgraded></iframe>

Learn more about 3D Maps in the [3D maps overview](https://developers.google.com/maps/documentation/javascript/3d/overview.md.txt).

### 45° Imagery (deprecated)

> [!WARNING]
> **[Deprecated:](https://developers.google.com/maps/deprecations.md.txt)** As of version 3.65,
> released in May 2026, Maps JavaScript API satellite and hybrid map types no longer automatically
> switch to 45° Imagery at higher zoom levels. Where 45° Imagery is available, the map shows the
> top-down satellite view even as the user zooms in. Calls to map.setTilt(45) are also ineffective.
> Customers and applications relying on 45° Imagery for their use-cases need to switch to
> alternatives, for example [3D maps](https://developers.google.com/maps/documentation/javascript/3d/overview.md.txt).

The following sample shows how to achieve a 45° perspective view of San Francisco using
[3D maps](https://developers.google.com/maps/documentation/javascript/3d/overview.md.txt):
<iframe src="https://maps-docs-team.web.app/samples/3d-map-45-degree/dist/" allow="fullscreen; "></iframe>

### TypeScript

```typescript
async function init() {
    const { Map3DElement } = await google.maps.importLibrary('maps3d');
    const map = new Map3DElement({
        center: {
            lat: 37.789,
            lng: -122.401,
            altitude: 0,
        },
        range: 2200,
        tilt: 45,
        heading: 188,
    });
    map.mode = 'SATELLITE';
    document.body.append(map);
}

void init();
```

### JavaScript

```javascript
async function init() {
    const { Map3DElement } = await google.maps.importLibrary('maps3d');
    const map = new Map3DElement({
        center: {
            lat: 37.789,
            lng: -122.401,
            altitude: 0,
        },
        range: 2200,
        tilt: 45,
        heading: 188,
    });
    map.mode = 'SATELLITE';
    document.body.append(map);
}

void init();
```

### CSS

```css
/* 
 * Always set the map height explicitly to define the size of the div element
 * that contains the map. 
 */
#gmp-map-3d {
    height: 100%;
}
html,
body {
    height: 100%;
    margin: 0;
    padding: 0;
}
```

### HTML

```html
<html>
    <head>
        <title>3d map 45-degree perspective</title>
        <link rel="stylesheet" type="text/css" href="./style.css" />
        <script type="module" src="./index.js"></script>
        <script>
            // prettier-ignore
            (g=>{var h,a,k,p="The Google Maps JavaScript API",c="google",l="importLibrary",q="__ib__",m=document,b=window;b=b[c]||(b[c]={});var d=b.maps||(b.maps={}),r=new Set,e=new URLSearchParams,u=()=>h||(h=new Promise(async(f,n)=>{await (a=m.createElement("script"));e.set("libraries",[...r]+"");for(k in g)e.set(k.replace(/[A-Z]/g,t=>"_"+t[0].toLowerCase()),g[k]);e.set("callback",c+".maps."+q);a.src=`https://maps.${c}apis.com/maps/api/js?`+e;d[q]=f;a.onerror=()=>h=n(Error(p+" could not load."));a.nonce=m.querySelector("script[nonce]")?.nonce||"";m.head.append(a)}));d[l]?console.warn(p+" only loads once. Ignoring:",g):d[l]=(f,...n)=>r.add(f)&&u().then(()=>d[l](f,...n))})({
                key: "GOOGLE_MAPS_API_KEY"
            });
        </script>
    </head>
    <body>
        <div id="map"></div>
    </body>
</html>
```
[View example](https://developers.google.com/maps/documentation/javascript/examples/3d-map-45-degree)

### Try Sample

[JSFiddle.net](https://jsfiddle.net/gh/get/library/pure/googlemaps-samples/js-api-samples/tree/main/dist/samples/3d-map-45-degree/jsfiddle)

## Modify the Map Type Registry

A map's `mapTypeId` is a string identifier
that is used to associate a `MapType` with a
unique value. Each `Map` object maintains a
`MapTypeRegistry` which contains the collection of
available `MapType`s for that map. This registry
is used to select the types of maps which are available in
the Map's MapType control, for example.

You don't read directly from the map type registry. Instead,
you modify the registry by adding custom map types and associating
them with a string identifier of your choosing. You cannot modify
or alter the basic map types (though you can remove them from the
map by altering the appearance of the map's associated
`mapTypeControlOptions`).

The following code sets the map to show only
two map types in the map's `mapTypeControlOptions`
and modifies the registry to add the association with
this identifier to the actual implementation of the
`MapType` interface.

> [!NOTE]
> **Note:** In the following code example, the creation of the custom map type itself is intentionally omitted. See [Styled Maps](https://developers.google.com/maps/documentation/javascript/maptypes.md.txt#StyledMaps) or [Custom Map Types](https://developers.google.com/maps/documentation/javascript/maptypes.md.txt#CustomMapTypes) below for information on constructing a map type.

```javascript
// 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);
```

## Styled Maps

The `StyledMapType` lets you customize the presentation of
the standard Google base maps, changing the visual display of such elements
as roads, parks, and built-up areas to reflect a different style than that
used in the default map type. The `StyledMapType` affects only the default `roadmap` map type.

For more information about the `StyledMapType`, see
[Using embedded JSON style
declarations](https://developers.google.com/maps/documentation/javascript/json-styling-overview).

> [!IMPORTANT]
> If you want to update the same style across multiple apps, look into [cloud customization](https://developers.google.com/maps/documentation/javascript/cloud-customization.md.txt), which is available in the Google Cloud console and requires a [map ID](https://developers.google.com/maps/documentation/get-map-id). To avoid potential conflicts, **do not combine** cloud customization and hardcoded styling in the same app.

## Custom Map Types

The Maps JavaScript API supports the display
and management of custom map types, allowing you to implement
your own map imagery or tile overlays.

Several possible map type implementations exist within the
Maps JavaScript API:

- Standard *tile sets* consisting of images which collectively constitute full cartographic maps. These tile sets are also known as *base* map types. These map types act and behave like the existing default map types: `roadmap`, `satellite`, `hybrid` and `terrain`. You can add your custom map type to a Map's `mapTypes` array to allow the UI within the Maps JavaScript API to treat your custom map type as a standard map type (by including it in the MapType control, for example).
- Image tile *overlays* which display on top of existing base map types. Generally, these map types are used to augment an existing map type to display additional information and are often constrained to specific locations and/or zoom levels. Note that these tiles may be transparent, allowing you to add features to existing maps.
- Non-image map types, which allow you to manipulate the display of map information at its most fundamental level.

Each of these options relies on creating a class that
implements the [`MapType`](https://developers.google.com/maps/documentation/javascript/maptypes.md.txt#MapTypeInterface)
interface. Additionally, the [`ImageMapType`](https://developers.google.com/maps/documentation/javascript/maptypes.md.txt#ImageMapTypes) class provides some built-in
behavior to simplify the creation of imagery map types.

### The `MapType` Interface

Before you create classes which implement `MapType`,
it is important to understand how Google Maps determines
coordinates and decides which parts of the map to show. You need to
implement similar logic for any base or overlay map types.
Read the guide to [map
and tile coordinates](https://developers.google.com/maps/documentation/javascript/coordinates).

Custom map types must implement the `MapType`
interface. This interface specifies certain properties and
methods that allow the API to initiate requests to your map
type(s) when the API determines that it needs to display map
tiles within the current viewport and zoom level. You handle
these requests to decide which tile to load.

**Note** : You may create your
own class to implement this interface. Alternatively, if you have
compatible imagery you can use the [`ImageMapType`](https://developers.google.com/maps/documentation/javascript/maptypes.md.txt#ImageMapTypes) class which already implements
this interface.

Classes implementing the `MapType` interface
require that you define and populate the following properties:

- `tileSize` (required) specifies the size of the tile (of type `google.maps.Size`). Sizes must be rectangular though they need not be square.
- `maxZoom` (required) specifies the maximum zoom level at which to display tiles of this map type.
- `minZoom` (optional) specifies the minimum zoom level at which to display tile of this map type. By default, this value is `0` indicating that no minimum zoom level exists.
- `name` (optional) specifies the name for this map type. This property is only necessary if you want this map type to be selectable within a MapType control. (See [Control Options](https://developers.google.com/maps/documentation/javascript/controls.md.txt#ControlOptions).)
- `alt` (optional) specifies the alternate text for this map type, exhibited as hover text. This property is only necessary if you want this map type to be selectable within a MapType control. (See [Control Options](https://developers.google.com/maps/documentation/javascript/controls.md.txt#ControlOptions).)

Additionally, classes implementing the `MapType` interface
need to implement the following methods:

- `getTile()` (required) is called whenever the API
  determines that the map needs to display new tiles for the given
  viewport. The `getTile()` method must have the following
  signature:


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

  The API determines whether it needs to call `getTile()`
  based on the `MapType`'s `tileSize`,
  `minZoom`, and `maxZoom`
  properties and the map's current viewport and zoom level. The handler
  for this method should return an HTML element given a passed coordinate,
  zoom level, and DOM element on which to append the tile image.
- `releaseTile()` (optional) is called whenever the API
  determines that the map needs to remove a tile as it falls out of view.
  This method must have the following signature:

  `releaseTile(tile:Node)`

  You typically should handle removal of any elements
  that were attached to the map tiles upon addition to the map.
  For example, if you attached event listeners to map tile
  overlays, you should remove them here.

The `getTile()` method acts as the main controller for
determining which tiles to load within a given viewport.

### Base Map Types

Map types which you construct in this manner may either stand
alone or be combined with other map types as overlays. Standalone
map types are known as *base map types* . You may want to have the API
treat such custom `MapType`s as it would any other existing
base map type (`ROADMAP`, `TERRAIN`, etc.). To do
so, add your custom `MapType` to the `Map`'s
`mapTypes` property. This property is of type
`MapTypeRegistry`.

The following code creates a base `MapType` to display
a map's tile coordinates and draws an outline of the tiles:

### TypeScript

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

> [!NOTE]
> **Note:** Read the [guide](https://developers.google.com/maps/documentation/javascript/using-typescript.md.txt) on using TypeScript and Google Maps.

### JavaScript

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

> [!NOTE]
> **Note:** The JavaScript is compiled from the TypeScript snippet.

[View example](https://developers.google.com/maps/documentation/javascript/examples/maptype-base.md.txt)

### Overlay Map Types

Some map types are designed to work on top of existing map
types. Such map types may have transparent layers indicating
points of interest, or showing additional data to the user.

In these cases, you do not want the map type treated as a separate entity but as an overlay.
You can do this by adding the map type to an existing `MapType` directly using
the `Map`'s `overlayMapTypes` property. This property contains
an `MVCArray` of `MapType`s. All map types
(base and overlay) are rendered within the
[`mapPane`](https://developers.google.com/maps/documentation/javascript/reference.md.txt#MapPanes)
layer. Overlay map types will display on top of the base map they
are attached to, in the order in which they appear in the
`Map.overlayMapTypes` array (overlays with higher index
values are displayed in front of overlays with lower index values).

The following example is identical to the previous one
except that we've created a tile overlay `MapType`
on top of the `ROADMAP` map type:

### TypeScript

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

> [!NOTE]
> **Note:** Read the [guide](https://developers.google.com/maps/documentation/javascript/using-typescript.md.txt) on using TypeScript and Google Maps.

### JavaScript

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

> [!NOTE]
> **Note:** The JavaScript is compiled from the TypeScript snippet.

[View example](https://developers.google.com/maps/documentation/javascript/examples/maptype-overlay.md.txt)

### Image Map Types

Implementing a `MapType` to act as a base map
type can be a time-consuming and laborious task. The API
provides a special class that implements the `MapType`
interface for the most common map types: map types that consist
of tiles made up of single image files.

This class, the `ImageMapType` class,
is constructed using an `ImageMapTypeOptions`
object specification defining the following required
properties:

- `tileSize` (required) specifies the size of the tile (of type `google.maps.Size`). Sizes must be rectangular though they need not be square.
- `getTileUrl` (required) specifies the function, usually provided as an inline function literal, to handle selection of the proper image tile based on supplied world coordinates and zoom level.

<br />

The following code implements a basic `ImageMapType`
using Google's moon tiles. The example makes use of a normalization
function to ensure that tiles repeat along the x-axis, but not along the
y-axis of your map.

### TypeScript

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

> [!NOTE]
> **Note:** Read the [guide](https://developers.google.com/maps/documentation/javascript/using-typescript.md.txt) on using TypeScript and Google Maps.

### JavaScript

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

> [!NOTE]
> **Note:** The JavaScript is compiled from the TypeScript snippet.

[View example](https://developers.google.com/maps/documentation/javascript/examples/maptype-image.md.txt)

## Projections

The Earth is a three-dimensional sphere (approximately), while a
map is a flat two-dimensional surface. The map that you see within
the Maps JavaScript API, like any flat map of the Earth, is a
*projection* of that sphere onto a flat surface. In its simplest
terms, a projection can be defined as a mapping of
latitude/longitude values into coordinates on the projection's map.

Projections in the Maps JavaScript API must implement the
`Projection` interface. A `Projection`
implementation must provide not only a mapping from one coordinate
system to another, but a bi-directional mapping. That is, you must
define how to translate from Earth coordinates (`LatLng` objects)
to the `Projection` class's
[world
coordinate](https://developers.google.com/maps/documentation/javascript/coordinates#world-coordinates) system, and from the world coordinate system back to the Earth coordinates.
Google Maps uses the Mercator projection to create its maps
from geographic data and convert events on the map into
geographic coordinates. You can obtain this projection by
calling `getProjection()` on the `Map`
(or any of the standard base `MapType` types.) For most uses,
this standard `Projection` will suffice, but you may also
define and use your own custom projections.

### Implement a Projection

When implementing a custom projection, you will need to define
a few things:

- The formulae for mapping latitude and longitude coordinates into a Cartesian plane and the corresponding formulae for mapping from a Cartesian plane to latitude and longitude coordinates. (The `Projection` interface only supports transformations into rectilinear coordinates.)
- The base tile size. All tiles must be rectangular.
- The "world size" of a map using the base tile set at zoom level 0. Note that for maps consisting of one tile at zoom 0, the world size and base tile size are identical.

### Coordinate Transformations in
Projections

Each projection provides two methods which translate between these two
coordinate systems, allowing you to convert between geographic and
world coordinates:

- The `Projection.fromLatLngToPoint()` method converts a `LatLng` value into a world coordinate. This method is used to position overlays on the map (and to position the map itself).
- The `Projection.fromPointToLatLng()` method converts a world coordinate into a `LatLng` value. This method is used to convert events such as clicks that happen on the map into geographic coordinates.

Google Maps assumes that projections are rectilinear.

Generally, you may use a projection for two cases: to create a
map of the world, or to create a map of a local area. In the former case,
you should ensure that your projection is also rectilinear and normal
at all longitudes. Some projections (especially conic projections)
may be "locally normal" (i.e. point north) but deviate from true north;
for example, the further the map is positioned relative to some
reference longitude. You may use such a projection locally, but be
aware that the projection is necessarily imprecise and transformation
errors will become increasingly apparently the further away from
the reference longitude you deviate.

### Map Tile Selection in Projections

Projections are not only useful for determining the positions of
locations or overlays, but for positioning the map tiles themselves.
The Maps JavaScript API renders base maps using a `MapType`
interface, which must declare both a `projection` property for
identifying the map's projection and a `getTile()`
method for retrieving map tiles based on
[tile
coordinate](https://developers.google.com/maps/documentation/javascript/coordinates#tile-coordinates) values. Tile coordinates are based on
both your basic tile size (which must be rectangular) and the "world
size" of your map, which is the pixel size of your map world
at zoom level 0. (For maps consisting of one tile at zoom 0, the tile
size and world size are identical.)

You define the base tile size within your `MapType`'s
`tileSize` property. You define the world size implicitly
within your projection's `fromLatLngToPoint()`
and `fromPointToLatLng()` methods.

Since image selection depends on these passed values, it is useful
to name images that can be selected programmatically given those
passed values, such as
`map_zoom_tileX_tileY.png`.

The following example defines an `ImageMapType`
using the
[Gall-Peters](https://en.wikipedia.org/wiki/Gall%E2%80%93Peters_projection) projection:

### TypeScript

```typescript
// This example defines an image map type using the Gall-Peters
// projection.
// https://en.wikipedia.org/wiki/Gall%E2%80%93Peters_projection
const ASSET_BASE_URL = new URL('https://maps-docs-team.web.app/samples/map-projection-simple/dist/');
const mapElement = document.querySelector('gmp-map')!;
let innerMap: google.maps.Map;

async function init() {
    // Request the needed libraries.
    await google.maps.importLibrary('maps');

    // Create a map.
    innerMap = mapElement.innerMap;
    innerMap.setOptions({
        mapTypeControl: false,
    });

    // Set the Gall-Peters map type.
    const gallPetersMapType = await initGallPeters();
    innerMap.mapTypes.set('gallPeters', gallPetersMapType);
    innerMap.setMapTypeId('gallPeters');

    // Show the lat and lng under the mouse cursor.
    const coordsDiv = document.getElementById('coords')!;

    innerMap.addListener('mousemove', (event: google.maps.MapMouseEvent) => {
        coordsDiv.textContent = `lat: ${String(Math.round(event.latLng!.lat()))}, lng: ${String(Math.round(event.latLng!.lng()))}`;
    });

    // Add some markers to the map.
    innerMap.data.setStyle((feature) => {
        return {
            title: feature.getProperty('name') as string,
            optimized: false,
        };
    });
    innerMap.data.addGeoJson(cities);
}

async function initGallPeters(): Promise<google.maps.ImageMapType> {
    const [{ ImageMapType }, { Size, Point, LatLng }] = await Promise.all([
        google.maps.importLibrary('maps'),
        google.maps.importLibrary('core'),
    ]);

    const GALL_PETERS_RANGE_X = 800;
    const GALL_PETERS_RANGE_Y = 512;

    // Fetch Gall-Peters tiles stored locally on our server.
    const gallPetersMapType = new ImageMapType({
        getTileUrl(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 new URL(
                `./gall-peters_${String(zoom)}_${String(x)}_${String(y)}.png`,
                ASSET_BASE_URL
            ).href;
        },
        tileSize: new 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(latLng: google.maps.LatLng) {
            const latRadians = (latLng.lat() * Math.PI) / 180;
            return new Point(
                GALL_PETERS_RANGE_X * (0.5 + latLng.lng() / 360),
                GALL_PETERS_RANGE_Y * (0.5 - 0.5 * Math.sin(latRadians))
            );
        },
        fromPointToLatLng(point: google.maps.Point, noWrap?: boolean) {
            const x = point.x / GALL_PETERS_RANGE_X;
            const y = Math.max(0, Math.min(1, point.y / GALL_PETERS_RANGE_Y));

            return new LatLng(
                (Math.asin(1 - 2 * y) * 180) / Math.PI,
                -180 + 360 * x,
                noWrap
            );
        },
    };

    return gallPetersMapType;
}

// 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' },
        },
    ],
};

void init();
```

### JavaScript

```javascript
// This example defines an image map type using the Gall-Peters
// projection.
// https://en.wikipedia.org/wiki/Gall%E2%80%93Peters_projection
const ASSET_BASE_URL = new URL('https://maps-docs-team.web.app/samples/map-projection-simple/dist/');
const mapElement = document.querySelector('gmp-map');
let innerMap;

async function init() {
    // Request the needed libraries.
    await google.maps.importLibrary('maps');

    // Create a map.
    innerMap = mapElement.innerMap;
    innerMap.setOptions({
        mapTypeControl: false,
    });

    // Set the Gall-Peters map type.
    const gallPetersMapType = await initGallPeters();
    innerMap.mapTypes.set('gallPeters', gallPetersMapType);
    innerMap.setMapTypeId('gallPeters');

    // Show the lat and lng under the mouse cursor.
    const coordsDiv = document.getElementById('coords');

    innerMap.addListener('mousemove', (event) => {
        coordsDiv.textContent = `lat: ${String(Math.round(event.latLng.lat()))}, lng: ${String(Math.round(event.latLng.lng()))}`;
    });

    // Add some markers to the map.
    innerMap.data.setStyle((feature) => {
        return {
            title: feature.getProperty('name'),
            optimized: false,
        };
    });
    innerMap.data.addGeoJson(cities);
}

async function initGallPeters() {
    const [{ ImageMapType }, { Size, Point, LatLng }] = await Promise.all([
        google.maps.importLibrary('maps'),
        google.maps.importLibrary('core'),
    ]);

    const GALL_PETERS_RANGE_X = 800;
    const GALL_PETERS_RANGE_Y = 512;

    // Fetch Gall-Peters tiles stored locally on our server.
    const gallPetersMapType = new ImageMapType({
        getTileUrl(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 new URL(
                `./gall-peters_${String(zoom)}_${String(x)}_${String(y)}.png`,
                ASSET_BASE_URL
            ).href;
        },
        tileSize: new 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(latLng) {
            const latRadians = (latLng.lat() * Math.PI) / 180;
            return new Point(
                GALL_PETERS_RANGE_X * (0.5 + latLng.lng() / 360),
                GALL_PETERS_RANGE_Y * (0.5 - 0.5 * Math.sin(latRadians))
            );
        },
        fromPointToLatLng(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 LatLng(
                (Math.asin(1 - 2 * y) * 180) / Math.PI,
                -180 + 360 * x,
                noWrap
            );
        },
    };

    return gallPetersMapType;
}

// 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' },
        },
    ],
};

void init();
export {};
```
[View example](https://developers.google.com/maps/documentation/javascript/examples/map-projection-simple.md.txt)

### Try Sample

[JSFiddle.net](https://jsfiddle.net/gh/get/library/pure/googlemaps-samples/js-api-samples/tree/main/dist/samples/map-projection-simple/jsfiddle)