## Overview

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

> [!NOTE]
> Also see the Maps JavaScript API Reference: [Rendering](https://developers.google.com/maps/documentation/javascript/reference/street-view.md.txt), [Service](https://developers.google.com/maps/documentation/javascript/reference/street-view-service.md.txt))

Google Street View provides panoramic 360 degree views from
designated roads throughout its coverage area. Street View's API
coverage is the same as that for the Google Maps application
(`https://maps.google.com/`). The list of currently
supported cities for Street View is available at the
[Google
Maps website](http://www.google.com/maps/about/behind-the-scenes/streetview/#where).

> [!NOTE]
> **Note:** Street View imagery [comes
> from two sources](https://www.google.com/streetview/): Google (non-public developer-controlled imagery), and the public (UGC, or public user-generated content). Note that there is no way to use only one source of Street View imagery over the other. Learn more about our [Street view imagery privacy policy](https://www.google.com/streetview/privacy/).

A sample Street View image is shown below.

<iframe src="https://geo-devrel-javascript-samples.web.app/samples/streetview-embed/app/dist/" allow="fullscreen; "></iframe>

The Maps JavaScript API provides a Street View service
for obtaining and manipulating the imagery used in Google Maps
Street View. This Street View service is supported natively within the
browser.

## Street View Map Usage

Although Street View can be used within a
[standalone DOM element](https://developers.google.com/maps/documentation/javascript/streetview.md.txt#StreetViewContainers), it is most
useful when indicating a location on a map. By default, Street View is
enabled on a map, and a Street View *Pegman control* appears
integrated within the navigation (zoom and pan) controls. You may hide this
control within the map's `MapOptions` by setting
`streetViewControl` to `false`. You may also change
the default position of the Street View control by
setting the `Map`'s `streetViewControlOptions.position`
property to a new `ControlPosition`.

The Street View Pegman control allows you to view Street View
panoramas directly within the map. When the user clicks and holds the Pegman,
the map updates to show blue outlines around Street View-enabled streets,
offering a user experience similar to the Google Maps app.

When the user drops the Pegman marker onto a street, the map updates to
display a Street View panorama of the indicated location.

## Street View Panoramas

> [!NOTE]
> **Note:** Only Street View panoramas generated with the `StreetViewPanorama` object are billed. The built-in Street View experience based on the Street View Pegman control, and the `StreetViewService`, are not billed. For more information, see [Pricing](https://developers.google.com/maps/documentation/javascript/usage-and-billing.md.txt#dynamic-street-view-pro-sku).

Street View images are supported through use of the
`StreetViewPanorama` object, which provides an API
interface to a Street View "viewer." Each map contains a default
Street View panorama, which you can retrieve by calling the
map's `getStreetView()` method. When you add a Street View
control to the map by setting its `streetViewControl`
option to `true`, you automatically connect the Pegman
control to this default Street View panorama.

You may also create your own `StreetViewPanorama`
object and set the map to use that instead of the default, by
setting the map's `streetView` property explicitly to
that constructed object. You may wish to override the default panorama
if you want to modify default behavior, such as the automatic
sharing of overlays between the map and the panorama. (See
[Overlays within Street View](https://developers.google.com/maps/documentation/javascript/streetview.md.txt#StreetViewOverlays) below.)

## Street View Containers

You may instead wish to display a `StreetViewPanorama`
within a separate DOM element, often a `<div>` element.
Simply pass the DOM element within the `StreetViewPanorama`'s
constructor. For optimum display of images, we recommend a minimum size of
200 pixels by 200 pixels.

**Note:** Although Street View
functionality is designed to be used in conjunction with a map, this
usage is not required. You may use a standalone Street View object
without a map.

## Street View Locations and Point-of-View (POV)

The `StreetViewPanorama` constructor also allows you to
set the Street View location and point of view using the
`StreetViewOptions` parameter. You may call `
setPosition()` and `setPov()` on the object after
construction to change its location and POV.

The Street View location defines the placement of the camera focus
for an image, but it does not define the orientation of the camera
for that image. For that purpose, the `StreetViewPov` object
defines two properties:

- `heading` (default `0`) defines the rotation angle around the camera locus in degrees relative from true north. Headings are measured clockwise (90 degrees is true east).
- `pitch` (default `0`) defines the angle variance "up" or "down" from the camera's initial default pitch, which is often (but not always) flat horizontal. (For example, an image taken on a hill will likely exhibit a default pitch that is not horizontal.) Pitch angles are measured with positive values looking up (to +90 degrees straight up and orthogonal to the default pitch) and negative values looking down (to -90 degrees straight down and orthogonal to the default pitch).

The `StreetViewPov` object is most often used to determine the
point of view of the Street View camera. You can also determine the
point-of-view of the photographer --- typically the direction the
[car
or trike](https://maps.google.com/help/maps/streetview/learn/cars-trikes-and-more.html) was facing --- with the
`StreetViewPanorama.getPhotographerPov()` method.

The following code displays a map of Boston with an initial view of Fenway
Park. Selecting the Pegman and dragging it to a supported location on the map
will change the Street View panorama:

### TypeScript

```typescript
async function init(): Promise<void> {
    // Request needed libraries.
    const [{ StreetViewPanorama }] = await Promise.all([
        google.maps.importLibrary('streetView'),
        google.maps.importLibrary('maps'),
    ]);

    // Get the gmp-map element and its innerMap.
    const mapElement = document.querySelector('gmp-map')!;
    const innerMap = mapElement.innerMap;

    // Get the center of the map.
    const position = mapElement.center as google.maps.LatLng;

    // Create a new StreetViewPanorama, set its position, and attach it to the map.
    const panorama = new StreetViewPanorama(document.getElementById('pano')!, {
        position,
        pov: {
            heading: 34,
            pitch: 10,
        },
    });

    /**
     * Set the StreetViewPanorama so it displays in the pano element
     * rather than taking over the map.
     * This creates a link between the map and the panorama so that
     * the map is updated when the StreetViewPanorama position changes,
     * and the StreetViewPanorama is updated when the map changes.
     */
    innerMap.setStreetView(panorama);
}

void init();
```

### JavaScript

```javascript
async function init() {
    // Request needed libraries.
    const [{ StreetViewPanorama }] = await Promise.all([
        google.maps.importLibrary('streetView'),
        google.maps.importLibrary('maps'),
    ]);

    // Get the gmp-map element and its innerMap.
    const mapElement = document.querySelector('gmp-map');
    const innerMap = mapElement.innerMap;

    // Get the center of the map.
    const position = mapElement.center;

    // Create a new StreetViewPanorama, set its position, and attach it to the map.
    const panorama = new StreetViewPanorama(document.getElementById('pano'), {
        position,
        pov: {
            heading: 34,
            pitch: 10,
        },
    });

    /**
     * Set the StreetViewPanorama so it displays in the pano element
     * rather than taking over the map.
     * This creates a link between the map and the panorama so that
     * the map is updated when the StreetViewPanorama position changes,
     * and the StreetViewPanorama is updated when the map changes.
     */
    innerMap.setStreetView(panorama);
}

void init();
```

### CSS

```css
html,
body {
    height: 100%;
    margin: 0;
    padding: 0;
}

body {
    display: flex;
}

gmp-map,
#pano {
    flex: 1;
    height: 100%;
}
```

### HTML

```html
<html>
    <head>
        <title>Street View split-map-panes</title>

        <link rel="stylesheet" type="text/css" href="./style.css" />
        <script type="module" src="./index.ts"></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>
        <gmp-map
            center="42.345573,-71.098326"
            zoom="14"
            map-id="DEMO_MAP_ID"></gmp-map>
        <div id="pano"></div>
    </body>
</html>
```
[View example](https://developers.google.com/maps/documentation/javascript/examples/streetview-simple.md.txt)

### Try Sample

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

## Motion tracking on mobile devices

On devices that support device orientation events, the API offers users the
ability to change the Street View point of view based on the movement of the
device. Users can look around by moving their devices. This is called motion
tracking or device rotation tracking.

As app developer, you can change the default behavior as follows:

- Enable or disable the motion tracking functionality. By default, motion tracking is enabled on any device that supports it. The following sample disables motion tracking, but leaves the motion tracking control visible. (Note that the user can turn on motion tracking by tapping the control.)

  ```javascript
  var panorama = new google.maps.StreetViewPanorama(
      document.getElementById('pano'), {
        position: {lat: 37.869260, lng: -122.254811},
        pov: {heading: 165, pitch: 0},
        motionTracking: false
      });
  ```
- Hide or show the motion tracking control. By default, the control is
  shown on devices that support motion tracking. The user can tap the
  control to turn motion tracking on or off. Note that the control will
  never appear if the device doesn't support motion tracking, regardless of
  the value of `motionTrackingControl`.

  The following sample disables both motion tracking and
  the motion tracking control. In this case, the user can't turn motion
  tracking on:

  ```javascript
  var panorama = new google.maps.StreetViewPanorama(
      document.getElementById('pano'), {
        position: {lat: 37.869260, lng: -122.254811},
        pov: {heading: 165, pitch: 0},
        motionTracking: false,
        motionTrackingControl: false
      });
  ```
- Change the default position of the motion tracking control. By default, the control appears near the bottom right of the panorama (position `RIGHT_BOTTOM`). The following sample sets the position of the control to left bottom:

  ```javascript
  var panorama = new google.maps.StreetViewPanorama(
      document.getElementById('pano'), {
        position: {lat: 37.869260, lng: -122.254811},
        pov: {heading: 165, pitch: 0},
        motionTrackingControlOptions: {
          position: google.maps.ControlPosition.LEFT_BOTTOM
        }
      });
  ```

To see motion tracking in action, view the following sample on a mobile
device (or any device that supports device orientation events):

<iframe src="https://geo-devrel-javascript-samples.web.app/samples/streetview-embed/app/dist/" allow="fullscreen; "></iframe>

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

## Overlays within Street View

The default `StreetViewPanorama` object supports the native
display of map
[overlays](https://developers.google.com/maps/documentation/javascript/overlays.md.txt).
Overlays generally appear at "street level" anchored at `LatLng`
positions. (Markers will appear with their tails anchored to the location's
horizontal plane within the Street View panorama for example.)

Currently, the types of overlays which are supported on Street View
panoramas are limited to `Marker`s, `InfoWindow`s and
custom `OverlayView`s. Overlays which you display on a map may be
displayed on a Street View panorama by treating the panorama as a substitute
for the `Map` object, calling `setMap()` and passing
the `StreetViewPanorama` as an argument instead of a map. Info
windows similarly may be opened within a Street View panorama by calling
`open()`, passing the `StreetViewPanorama()` instead of
a map.

Additionally, when creating a map with a default
`StreetViewPanorama`, any markers created on a map are shared
automatically with the map's associated Street View panorama, provided that
panorama is visible. To retrieve the default Street View panorama, call
`getStreetView()` on the `Map` object. Note that if you
explicitly set the map's `streetView` property to a
`StreetViewPanorama` of your own construction, you will override
the default panorama.

The following example shows markers denoting various locations around
Astor Place, New York City. Toggle the display to Street View to show the
shared markers displaying within the `StreetViewPanorama`.

### TypeScript

```typescript
const ASSET_BASE_URL = new URL('https://maps-docs-team.web.app/samples/streetview-overlays/dist/');

let panorama: google.maps.StreetViewPanorama;
let innerMap: google.maps.Map;

async function init() {
    // Request needed libraries.
    const [{ Marker }] = await Promise.all([
        google.maps.importLibrary('marker'),
        google.maps.importLibrary('maps'),
    ]);

    // Set the location of Astor Place.
    const astorPlace = { lat: 40.729884, lng: -73.990988 };

    const mapElement = document.querySelector('gmp-map')!;

    innerMap = mapElement.innerMap;

    document
        .getElementById('streetview-toggle-button')!
        .addEventListener('click', toggleStreetView);

    const cafeIcon = document.createElement('img');
    cafeIcon.src = new URL('./cafe_icon.svg', ASSET_BASE_URL).href;

    const dollarIcon = document.createElement('img');
    dollarIcon.src = new URL('./bank_icon.svg', ASSET_BASE_URL).href;

    const busIcon = document.createElement('img');
    busIcon.src = new URL('./bus_icon.svg', ASSET_BASE_URL).href;

    // Set up the markers on the map
    new Marker({
        position: { lat: 40.730031, lng: -73.991428 },
        map: innerMap,
        title: 'Cafe',
        icon: cafeIcon.src,
    });

    new Marker({
        position: { lat: 40.729681, lng: -73.991138 },
        map: innerMap,
        title: 'Bank',
        icon: dollarIcon.src,
    });

    new Marker({
        position: { lat: 40.729559, lng: -73.990741 },
        map: innerMap,
        title: 'Bus Stop',
        icon: busIcon.src,
    });

    // We get the map's default panorama and set up some defaults.
    // Note that we don't yet set it visible.
    panorama = innerMap.getStreetView()!; // TODO fix type
    panorama.setPosition(astorPlace);
    panorama.setPov({
        heading: 265,
        pitch: 0,
    });
}

function toggleStreetView(): void {
    const toggle = panorama.getVisible();

    if (!toggle) {
        panorama.setVisible(true);
    } else {
        panorama.setVisible(false);
    }
}

void init();
```

### JavaScript

```javascript
const ASSET_BASE_URL = new URL('https://maps-docs-team.web.app/samples/streetview-overlays/dist/');

let panorama;
let innerMap;

async function init() {
    // Request needed libraries.
    const [{ Marker }] = await Promise.all([
        google.maps.importLibrary('marker'),
        google.maps.importLibrary('maps'),
    ]);

    // Set the location of Astor Place.
    const astorPlace = { lat: 40.729884, lng: -73.990988 };

    const mapElement = document.querySelector('gmp-map');

    innerMap = mapElement.innerMap;

    document
        .getElementById('streetview-toggle-button')
        .addEventListener('click', toggleStreetView);

    const cafeIcon = document.createElement('img');
    cafeIcon.src = new URL('./cafe_icon.svg', ASSET_BASE_URL).href;

    const dollarIcon = document.createElement('img');
    dollarIcon.src = new URL('./bank_icon.svg', ASSET_BASE_URL).href;

    const busIcon = document.createElement('img');
    busIcon.src = new URL('./bus_icon.svg', ASSET_BASE_URL).href;

    // Set up the markers on the map
    new Marker({
        position: { lat: 40.730031, lng: -73.991428 },
        map: innerMap,
        title: 'Cafe',
        icon: cafeIcon.src,
    });

    new Marker({
        position: { lat: 40.729681, lng: -73.991138 },
        map: innerMap,
        title: 'Bank',
        icon: dollarIcon.src,
    });

    new Marker({
        position: { lat: 40.729559, lng: -73.990741 },
        map: innerMap,
        title: 'Bus Stop',
        icon: busIcon.src,
    });

    // We get the map's default panorama and set up some defaults.
    // Note that we don't yet set it visible.
    panorama = innerMap.getStreetView(); // TODO fix type
    panorama.setPosition(astorPlace);
    panorama.setPov({
        heading: 265,
        pitch: 0,
    });
}

function toggleStreetView() {
    const toggle = panorama.getVisible();

    if (!toggle) {
        panorama.setVisible(true);
    } else {
        panorama.setVisible(false);
    }
}

void init();
export {};
```

### CSS

```css
/* 
 * Always set the map height explicitly to define the size of the div element
 * that contains the map. 
 */
#map {
    height: 100%;
}

/* 
 * Optional: Makes the sample page fill the window. 
 */
html,
body {
    height: 100%;
    margin: 0;
    padding: 0;
}

#floating-panel {
    position: absolute;
    top: 10px;
    left: 25%;
    z-index: 5;
    background-color: #fff;
    padding: 5px;
    border: 1px solid #999;
    text-align: center;
    font-family: 'Roboto', sans-serif;
    line-height: 30px;
    padding-left: 10px;
}

#streetview-toggle-button {
    height: 40px;
    display: flex;
    align-items: center;
    justify-content: center;
    padding: 0 17px;
    border: none;
    background: white;
    cursor: pointer;
    border-radius: 2px;
    box-shadow: 0 1px 4px -1px rgba(0, 0, 0, 0.3);
    margin: 10px 0px 10px -2px;
    font-family: Roboto, Arial, sans-serif;
    font-size: 18px;
    font-weight: 400;
    color: rgb(86, 86, 86);
}

#streetview-toggle-button:hover {
    background: #f4f4f4;
    color: #000;
}
```

### HTML

```html
<html>
    <head>
        <title>Overlays Within Street View</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>
        <gmp-map map-id="DEMO_MAP_ID" center="40.729884, -73.990988" zoom="18">
            <input
                type="button"
                value="Toggle Street View"
                id="streetview-toggle-button"
                slot="control-block-start-inline-start" />
        </gmp-map>
    </body>
</html>
```
[View example](https://developers.google.com/maps/documentation/javascript/examples/streetview-overlays.md.txt)

### Try Sample

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

## Street View Events

When navigating between Street View or manipulating its
orientation, you may wish to monitor several events that indicate
changes to the `StreetViewPanorama`'s state:

- `pano_changed` fires whenever the individual pano ID changes. This event does not guarantee that any associated data within the panorama (such as the links) has also changed by the time this event is triggered; this event only indicates that a pano ID has changed. Note that the pano ID (which you can use to reference this panorama) is only stable within the current browser session.
- `position_changed` fires whenever the underlying (`LatLng`) position of the panorama changes. Rotating a panorama will not trigger this event. Note that you could change a panorama's underlying position without changing the associated pano ID, since the API will automatically associate the nearest pano ID to the panorama's position.
- `pov_changed` fires whenever the Street View's `StreetViewPov` changes. Note that this event may fire while the position, and pano ID, remain stable.
- `links_changed` fires whenever the Street View's links change. Note that this event may fire asynchronously after a change in the pano ID indicated through `pano_changed`.
- `visible_changed` fires whenever the Street View's visibility changes. Note that this event may fire asynchronously after a change in the pano ID indicated through `pano_changed`.

The following code illustrates how these events can be handled
to collect data about the underlying `StreetViewPanorama`:

### TypeScript

```typescript
function initPano() {
  const panorama = new google.maps.StreetViewPanorama(
    document.getElementById("pano") as HTMLElement,
    {
      position: { lat: 37.869, lng: -122.255 },
      pov: {
        heading: 270,
        pitch: 0,
      },
      visible: true,
    }
  );

  panorama.addListener("pano_changed", () => {
    const panoCell = document.getElementById("pano-cell") as HTMLElement;

    panoCell.innerHTML = panorama.getPano();
  });

  panorama.addListener("links_changed", () => {
    const linksTable = document.getElementById("links_table") as HTMLElement;

    while (linksTable.hasChildNodes()) {
      linksTable.removeChild(linksTable.lastChild as ChildNode);
    }

    const links = panorama.getLinks();

    for (const i in links) {
      const row = document.createElement("tr");

      linksTable.appendChild(row);

      const labelCell = document.createElement("td");

      labelCell.innerHTML = "<b>Link: " + i + "</b>";

      const valueCell = document.createElement("td");

      valueCell.innerHTML = links[i].description as string;
      linksTable.appendChild(labelCell);
      linksTable.appendChild(valueCell);
    }
  });

  panorama.addListener("position_changed", () => {
    const positionCell = document.getElementById(
      "position-cell"
    ) as HTMLElement;

    (positionCell.firstChild as HTMLElement).nodeValue =
      panorama.getPosition() + "";
  });

  panorama.addListener("pov_changed", () => {
    const headingCell = document.getElementById("heading-cell") as HTMLElement;
    const pitchCell = document.getElementById("pitch-cell") as HTMLElement;

    (headingCell.firstChild as HTMLElement).nodeValue =
      panorama.getPov().heading + "";
    (pitchCell.firstChild as HTMLElement).nodeValue =
      panorama.getPov().pitch + "";
  });
}

declare global {
  interface Window {
    initPano: () => void;
  }
}
window.initPano = initPano;
```

> [!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 initPano() {
  const panorama = new google.maps.StreetViewPanorama(
    document.getElementById("pano"),
    {
      position: { lat: 37.869, lng: -122.255 },
      pov: {
        heading: 270,
        pitch: 0,
      },
      visible: true,
    },
  );

  panorama.addListener("pano_changed", () => {
    const panoCell = document.getElementById("pano-cell");

    panoCell.innerHTML = panorama.getPano();
  });
  panorama.addListener("links_changed", () => {
    const linksTable = document.getElementById("links_table");

    while (linksTable.hasChildNodes()) {
      linksTable.removeChild(linksTable.lastChild);
    }

    const links = panorama.getLinks();

    for (const i in links) {
      const row = document.createElement("tr");

      linksTable.appendChild(row);

      const labelCell = document.createElement("td");

      labelCell.innerHTML = "<b>Link: " + i + "</b>";

      const valueCell = document.createElement("td");

      valueCell.innerHTML = links[i].description;
      linksTable.appendChild(labelCell);
      linksTable.appendChild(valueCell);
    }
  });
  panorama.addListener("position_changed", () => {
    const positionCell = document.getElementById("position-cell");

    positionCell.firstChild.nodeValue = panorama.getPosition() + "";
  });
  panorama.addListener("pov_changed", () => {
    const headingCell = document.getElementById("heading-cell");
    const pitchCell = document.getElementById("pitch-cell");

    headingCell.firstChild.nodeValue = panorama.getPov().heading + "";
    pitchCell.firstChild.nodeValue = panorama.getPov().pitch + "";
  });
}

window.initPano = initPano;
```

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

### CSS

```css
/* 
 * Always set the map height explicitly to define the size of the div element
 * that contains the map. 
 */
#map {
  height: 100%;
}

/* 
 * Optional: Makes the sample page fill the window. 
 */
html,
body {
  height: 100%;
  margin: 0;
  padding: 0;
}

#floating-panel {
  position: absolute;
  top: 10px;
  left: 25%;
  z-index: 5;
  background-color: #fff;
  padding: 5px;
  border: 1px solid #999;
  text-align: center;
  font-family: "Roboto", "sans-serif";
  line-height: 30px;
  padding-left: 10px;
}

#pano {
  width: 50%;
  height: 100%;
  float: left;
}

#floating-panel {
  width: 45%;
  height: 100%;
  float: right;
  text-align: left;
  overflow: auto;
  position: static;
  border: 0px solid #999;
}
```

### HTML

```html
<html>
  <head>
    <title>Street View Events</title>

    <link rel="stylesheet" type="text/css" href="./style.css" />
    <script type="module" src="./index.js"></script>
  </head>
  <body>
    <div id="pano"></div>
    <div id="floating-panel">
      <table>
        <tr>
          <td><b>Position</b></td>
          <td id="position-cell">&nbsp;</td>
        </tr>
        <tr>
          <td><b>POV Heading</b></td>
          <td id="heading-cell">270</td>
        </tr>
        <tr>
          <td><b>POV Pitch</b></td>
          <td id="pitch-cell">0.0</td>
        </tr>
        <tr>
          <td><b>Pano ID</b></td>
          <td id="pano-cell">&nbsp;</td>
        </tr>
        <table id="links_table"></table>
      </table>
    </div>

    <!-- 
      The `defer` attribute causes the script to execute after the full HTML
      document has been parsed. For non-blocking uses, avoiding race conditions,
      and consistent behavior across browsers, consider loading using Promises. See
      https://developers.google.com/maps/documentation/javascript/load-maps-js-api
      for more information.
      -->
    <script
      src="https://maps.googleapis.com/maps/api/js?key=AIzaSyB41DRUbKWJHPxaFjMAwdrzWzbVKartNGg&callback=initPano&v=weekly"
      defer
    ></script>
  </body>
</html>
```
[View example](https://developers.google.com/maps/documentation/javascript/examples/streetview-events.md.txt)

## Street View Controls

When displaying a `StreetViewPanorama`, a variety of
controls appear on the panorama by default. You can enable or disable
these controls by setting their appropriate fields within the
[`StreetViewPanoramaOptions`](https://developers.google.com/maps/documentation/javascript/3.exp/reference#StreetViewPanoramaOptions)
to `true` or `false`:

- A `panControl` provides a way to rotate the panorama. This control appears by default as a standard integrated compass and pan control. You may alter the control's position by providing `PanControlOptions` within the `panControlOptions` field.
- A `zoomControl` provides a way to zoom within the image. This control appears by default near the bottom right of the panorama. You can alter the control's appearance by providing `ZoomControlOptions` within the `zoomControlOptions` field.
- An `addressControl` provides a textual overlay indicating the address of the associated location, and offers a link to open the location in Google Maps. You can alter the control's appearance by providing `StreetViewAddressControlOptions` within the `addressControlOptions` field.
- A `fullscreenControl` offers the option to open Street View in fullscreen mode. You can alter the control's appearance by providing `FullscreenControlOptions` within the `fullscreenControlOptions` field.
- A `motionTrackingControl` offers the option to enable or disable motion tracking on mobile devices. This control appears only on devices that support device orientation events. By default, the control appears near the bottom right of the panorama. You can alter the control's position by providing `MotionTrackingControlOptions`. For more information, see the section on [motion
  tracking](https://developers.google.com/maps/documentation/javascript/streetview#motion-tracking).
- A `linksControl` provides guide arrows on the image for traveling to adjacent panorama images.
- A Close control allows the user to close the Street View viewer. You can enable or disable the Close control by setting `enableCloseButton` to `true` or `false`.

The following example alters the controls displayed within the associated
Street View and removes the view's links:

### TypeScript

```typescript
function initPano() {
  // Note: constructed panorama objects have visible: true
  // set by default.
  const panorama = new google.maps.StreetViewPanorama(
    document.getElementById("map") as HTMLElement,
    {
      position: { lat: 42.345573, lng: -71.098326 },
      addressControlOptions: {
        position: google.maps.ControlPosition.BOTTOM_CENTER,
      },
      linksControl: false,
      panControl: false,
      enableCloseButton: false,
    }
  );
}

declare global {
  interface Window {
    initPano: () => void;
  }
}
window.initPano = initPano;
```

> [!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 initPano() {
  // Note: constructed panorama objects have visible: true
  // set by default.
  const panorama = new google.maps.StreetViewPanorama(
    document.getElementById("map"),
    {
      position: { lat: 42.345573, lng: -71.098326 },
      addressControlOptions: {
        position: google.maps.ControlPosition.BOTTOM_CENTER,
      },
      linksControl: false,
      panControl: false,
      enableCloseButton: false,
    },
  );
}

window.initPano = initPano;
```

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

### CSS

```css
/* 
 * Always set the map height explicitly to define the size of the div element
 * that contains the map. 
 */
#map {
  height: 100%;
}

/* 
 * Optional: Makes the sample page fill the window. 
 */
html,
body {
  height: 100%;
  margin: 0;
  padding: 0;
}
```

### HTML

```html
<html>
  <head>
    <title>Street View Controls</title>

    <link rel="stylesheet" type="text/css" href="./style.css" />
    <script type="module" src="./index.js"></script>
  </head>
  <body>
    <div id="map"></div>

    <!-- 
      The `defer` attribute causes the script to execute after the full HTML
      document has been parsed. For non-blocking uses, avoiding race conditions,
      and consistent behavior across browsers, consider loading using Promises. See
      https://developers.google.com/maps/documentation/javascript/load-maps-js-api
      for more information.
      -->
    <script
      src="https://maps.googleapis.com/maps/api/js?key=AIzaSyB41DRUbKWJHPxaFjMAwdrzWzbVKartNGg&callback=initPano&v=weekly"
      defer
    ></script>
  </body>
</html>
```
[View example](https://developers.google.com/maps/documentation/javascript/examples/streetview-controls.md.txt)

## Directly Accessing Street View Data

You may wish to programmatically determine the availability of
Street View data, or return information about particular panoramas,
without requiring direct manipulation of a map/panorama. You may do so
using the `StreetViewService` object, which provides an
interface to the data stored in Google's Street View service.

### Street View Service Requests

Accessing the Street View service is asynchronous, since the Google Maps API
needs to make a call to an external server. For that reason, you need to pass
a *callback* method to execute upon completion of the request. This
callback method processes the result.

You can initiate requests to the `StreetViewService` using
`StreetViewPanoRequest` or
`StreetViewLocationRequest`.

A request using `StreetViewPanoRequest` returns panorama
data given a reference ID which uniquely identifies the panorama. Note that
these reference IDs are only stable for the lifetime of the imagery of that
panorama.

A request using `StreetViewLocationRequest` searches for
panorama data at a specified location, using the following parameters:

- `location` specifies the location (latitude and longitude) to search for a panorama.
- `preference` sets a preference for which panorama should be found within the radius: the one nearest to the provided location, or the best one within the radius.
- `radius` sets a radius, specified in meters, in which to search for a panorama, centered on the given latitude and longitude. Defaults to 50 when not supplied.
- `source` specifies the source of panoramas to search. Valid values are:
  - `default` uses the default sources for Street View; searches are not limited to specific sources.
  - `outdoor` limits searches to outdoor collections. Note that outdoor panoramas may not exist for the specified location.

### Street View Service Responses

The function `getPanorama()`
needs a *callback* function to execute upon retrieval of a result
from the Street View service. This callback function returns a set of
panorama data within a `StreetViewPanoramaData` object and a
`StreetViewStatus` code denoting the status of the request, in
that order.

A `StreetViewPanoramaData` object specification contains
meta-data about a Street View panorama of the following form:

```javascript
{
  "location": {
    "latLng": LatLng,
    "description": string,
    "pano": string
  },
  "copyright": string,
  "links": [{
      "heading": number,
      "description": string,
      "pano": string,
      "roadColor": string,
      "roadOpacity": number
    }],
  "tiles": {
    "worldSize": Size,
    "tileSize": Size,
    "centerHeading": number
  }
}
```

Note that this data object is not a `StreetViewPanorama`
object itself. To create a Street View object using this data, you would
need to create a `StreetViewPanorama` and call
`setPano()`, passing it the ID as noted in the returned
`location.pano` field.

The `status` code may return one of the following values:

- `OK` indicates that the service found a matching panorama.
- `ZERO_RESULTS` indicates that the service could not find a matching panorama with the passed criteria.
- `UNKNOWN_ERROR` indicates that a Street View request could not be processed, though the exact reason is unknown.

The following code creates a `StreetViewService`
that responds to user clicks on a map by creating markers which,
when clicked, display a `StreetViewPanorama` of that
location. The code uses the contents of `StreetViewPanoramaData`
returned from the service.

### TypeScript

```typescript
/*
 * Click the map to set a new location for the Street View camera.
 */

let map: google.maps.Map;

let panorama: google.maps.StreetViewPanorama;

function initMap(): void {
  const berkeley = { lat: 37.869085, lng: -122.254775 };
  const sv = new google.maps.StreetViewService();

  panorama = new google.maps.StreetViewPanorama(
    document.getElementById("pano") as HTMLElement
  );

  // Set up the map.
  map = new google.maps.Map(document.getElementById("map") as HTMLElement, {
    center: berkeley,
    zoom: 16,
    streetViewControl: false,
  });

  // Set the initial Street View camera to the center of the map
  sv.getPanorama({ location: berkeley, radius: 50 }).then(processSVData);

  // Look for a nearby Street View panorama when the map is clicked.
  // getPanorama will return the nearest pano when the given
  // radius is 50 meters or less.
  map.addListener("click", (event) => {
    sv.getPanorama({ location: event.latLng, radius: 50 })
      .then(processSVData)
      .catch((e) =>
        console.error("Street View data not found for this location.")
      );
  });
}

function processSVData({ data }: google.maps.StreetViewResponse) {
  const location = data.location!;

  const marker = new google.maps.Marker({
    position: location.latLng,
    map,
    title: location.description,
  });

  panorama.setPano(location.pano as string);
  panorama.setPov({
    heading: 270,
    pitch: 0,
  });
  panorama.setVisible(true);

  marker.addListener("click", () => {
    const markerPanoID = location.pano;

    // Set the Pano to use the passed panoID.
    panorama.setPano(markerPanoID as string);
    panorama.setPov({
      heading: 270,
      pitch: 0,
    });
    panorama.setVisible(true);
  });
}

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
/*
 * Click the map to set a new location for the Street View camera.
 */
let map;
let panorama;

function initMap() {
  const berkeley = { lat: 37.869085, lng: -122.254775 };
  const sv = new google.maps.StreetViewService();

  panorama = new google.maps.StreetViewPanorama(
    document.getElementById("pano"),
  );
  // Set up the map.
  map = new google.maps.Map(document.getElementById("map"), {
    center: berkeley,
    zoom: 16,
    streetViewControl: false,
  });
  // Set the initial Street View camera to the center of the map
  sv.getPanorama({ location: berkeley, radius: 50 }).then(processSVData);
  // Look for a nearby Street View panorama when the map is clicked.
  // getPanorama will return the nearest pano when the given
  // radius is 50 meters or less.
  map.addListener("click", (event) => {
    sv.getPanorama({ location: event.latLng, radius: 50 })
      .then(processSVData)
      .catch((e) =>
        console.error("Street View data not found for this location."),
      );
  });
}

function processSVData({ data }) {
  const location = data.location;
  const marker = new google.maps.Marker({
    position: location.latLng,
    map,
    title: location.description,
  });

  panorama.setPano(location.pano);
  panorama.setPov({
    heading: 270,
    pitch: 0,
  });
  panorama.setVisible(true);
  marker.addListener("click", () => {
    const markerPanoID = location.pano;

    // Set the Pano to use the passed panoID.
    panorama.setPano(markerPanoID);
    panorama.setPov({
      heading: 270,
      pitch: 0,
    });
    panorama.setVisible(true);
  });
}

window.initMap = initMap;
```

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

### CSS

```css
/* 
 * Always set the map height explicitly to define the size of the div element
 * that contains the map. 
 */
#map {
  height: 100%;
}

/* 
 * Optional: Makes the sample page fill the window. 
 */
html,
body {
  height: 100%;
  margin: 0;
  padding: 0;
}
```

### HTML

```html
<html>
  <head>
    <title>Directly Accessing Street View Data</title>

    <link rel="stylesheet" type="text/css" href="./style.css" />
    <script type="module" src="./index.js"></script>
  </head>
  <body>
    <div id="map" style="width: 45%; height: 100%; float: left"></div>
    <div id="pano" style="width: 45%; height: 100%; float: left"></div>

    <!-- 
      The `defer` attribute causes the script to execute after the full HTML
      document has been parsed. For non-blocking uses, avoiding race conditions,
      and consistent behavior across browsers, consider loading using Promises. See
      https://developers.google.com/maps/documentation/javascript/load-maps-js-api
      for more information.
      -->
    <script
      src="https://maps.googleapis.com/maps/api/js?key=AIzaSyB41DRUbKWJHPxaFjMAwdrzWzbVKartNGg&callback=initMap&v=weekly"
      defer
    ></script>
  </body>
</html>
```
[View example](https://developers.google.com/maps/documentation/javascript/examples/streetview-service.md.txt)

## Providing Custom Street View Panoramas

The Maps JavaScript API supports the display of custom panoramas
within the `StreetViewPanorama` object. Using custom
panoramas, you can display the interior of buildings, views from
scenic locations, or anything from your imagination. You can even
link these custom panoramas to Google's existing Street View
panoramas.

Setting up a set of custom panorama imagery involves the
following steps:

- Create a base panoramic image for each custom panorama. This base image should be at the highest resolution image with which you wish to serve zoomed in imagery.
- (Optional, but recommended) Create a set of panoramic tiles at different zoom levels from the basic image.
- Create links between your custom panoramas.
- (Optional) Designate "entry" panoramas within Google's existing Street View imagery and customize links to/from the custom set to the standard set.
- Define metadata for each panorama image within a `StreetViewPanoramaData` object.
- Implement a method which determines the custom panorama data and images and designate that method as your custom handler within the `StreetViewPanorama` object.

The following sections explain this process.

### Creating Custom Panoramas

Each Street View panorama is an image or set of images
that provides a full 360 degree view from a single location.
The `StreetViewPanorama` object
uses images that conform to the equirectangular (Plate
Carrée) projection. Such a projection contains 360 degrees
of horizontal view (a full wrap-around) and 180 degrees of
vertical view (from straight up to straight down). These fields
of view result in an image with an aspect ratio of 2:1. A
full wrap-around panorama is shown below.

![Panorama view of a city street](https://developers.google.com/static/maps/documentation/javascript/images/samplePanorama.png)

Panorama images are generally obtained by taking multiple
photos from one position and stitching them together using
panorama software. (See Wikipedia's
[Comparison of photo stitching applications](https://en.wikipedia.org/wiki/Comparison_of_photo_stitching_applications_(software)) for more information.)
Such images should share a single "camera"
locus, from which each of the panorama images are taken. The
resulting 360 degree panorama can then define a projection on
a sphere with the image wrapped to the two-dimensional surface
of the sphere.

![Sphere with a panorama view of a street on its surface](https://developers.google.com/static/maps/documentation/javascript/images/panoramaSphere.png)

Treating the panorama as a projection on a sphere with a rectilinear
coordinate system is advantageous when dividing up the image into
rectilinear *tiles*, and serving images based on computed tile
coordinates.

### Creating Custom Panorama Tiles

Street View also supports different levels of image detail through
the use of a zoom control, which allows you to zoom in and out from the
default view. Generally, Street View provides five
levels of zoom resolution for any given panorama image. If you were to
rely on a single panorama image to serve all zoom levels, such an image
would either necessarily be quite large and significantly slow down
your application, or be of such poor resolution at higher zoom levels
that you would serve a poorly pixellated image. Luckily, however, we can use
a similar design pattern used to serve
[Google's map tiles](https://developers.google.com/maps/documentation/javascript/coordinates.md.txt#tile-coordinates) at different zoom levels to provide
appropriate resolution imagery for panoramas at each zoom level.

When a `StreetViewPanorama` first loads, by default
it displays an image consisting of 25% (90 degrees of arc) of the
horizontal breadth of the panorama at zoom level 1. This view
corresponds roughly with a normal human field of view. Zooming
"out" from this default view essentially provides a wider arc,
while zooming in narrows the field of a view to a smaller arc. The
`StreetViewPanorama` automatically calculates the
appropriate field of view for the selected zoom level, and then
selects imagery most appropriate for that resolution by selecting
a tile set that roughly matches the dimensions of the horizontal
field of view. The following fields of view map to Street View
zoom levels:

| Street View zoom level | Field of View (degrees) |
|---|---|
| 0 | 180 |
| 1 (default) | 90 |
| 2 | 45 |
| 3 | 22.5 |
| 4 | 11.25 |

Note that the size of the image shown within Street View is
entirely dependent on the screen size (width) of the Street View
container. If you provide a wider container, the service
will still provide the same field of view for any given
zoom level, though it may select tiles more appropriate
for that resolution instead.

Because each panorama consists of an equirectangular projection,
creating panorama tiles is relatively easy. As the projection
provides an image with an aspect ratio of 2:1, tiles with 2:1 ratios
are easier to use, though square tiles may provide better performance
on square maps (since the field of view will be square).

For 2:1 tiles, a single image encompassing the entire panorama
represents the entire panorama "world" (the base image) at zoom
level 0, with each increasing zoom level offering
4^*zoomLevel*^ tiles. (E.g. at
zoom level 2, the entire panorama consists of 16 tiles.)
Note: zoom levels in Street View
tiling do not match directly to zoom levels as provided using
the Street View control; instead the Street View control zoom
levels select a Field of View (FoV), from which appropriate
tiles are selected.

![Panorama view of a city street divided into tiles](https://developers.google.com/static/maps/documentation/javascript/images/panoramaTiles.png)

Generally, you will want to name your image tiles so they can be
selected programmatically. Such a naming scheme is
discussed below in [Handling Custom
Panorama Requests](https://developers.google.com/maps/documentation/javascript/streetview#CustomPanoramaMethod).

### Handling Custom Panorama Requests

To use a custom panorama, call
`StreetViewPanorama.registerPanoProvider()`, specifying the name
of your custom panorama provider method. The panorama provider
method must return a `StreetViewPanoramaData` object, and has the
following signature:

```javascript
Function(pano):StreetViewPanoramaData
```

A `StreetViewPanoramaData` is an object of the following
form:

```javascript
{
  copyright: string,
  location: {
    description: string,
    latLng: google.maps.LatLng,
    pano: string
  },
  tiles: {
    tileSize: google.maps.Size,
    worldSize: google.maps.Size,
    heading: number,
    getTileUrl: Function
  },
  links: [
    description: string,
    heading: number,
    pano: string,
    roadColor: string,
    roadOpacity: number
  ]
}
```

Display a custom panorama as follows:

- Set the [`StreetViewPanoramaOptions.pano`](https://developers.google.com/maps/documentation/javascript/3.exp/reference#StreetViewPanoramaOptions) property to a custom value.
- Call [`StreetViewPanorama.registerPanoProvider()`](https://developers.google.com/maps/documentation/javascript/3.exp/reference#StreetViewPanorama) to supply a custom panorama provider function.
- Implement your custom panorama provider function to handle the specified `pano` value.
- Construct a [`StreetViewPanoramaData`](https://developers.google.com/maps/documentation/javascript/3.exp/reference#StreetViewPanoramaData) object.
- Set the [`StreetViewTileData.getTileUrl`](https://developers.google.com/maps/documentation/javascript/3.exp/reference#StreetViewTileData) property to the name of a custom tile provider function that you supply. For example, `getCustomPanoramaTileUrl`.
- Implement your custom tile provider function, as shown in the samples below.
- Return the `StreetViewPanoramaData` object.

**Note:** Do not directly set a `position`
on the `StreetViewPanorama` when you wish to display
custom panoramas, as such a position will instruct the Street
View service to request the default Street View imagery close
to that location. Instead, set this position within the
custom `StreetViewPanoramaData` object's
`location.latLng` field.

The following example displays a custom panorama of the Google Sydney
office. Note that that this example doesn't use a map or default Street View
imagery:

### TypeScript

```typescript
function initPano() {
  // Set up Street View and initially set it visible. Register the
  // custom panorama provider function. Set the StreetView to display
  // the custom panorama 'reception' which we check for below.
  const panorama = new google.maps.StreetViewPanorama(
    document.getElementById("map") as HTMLElement,
    { pano: "reception", visible: true }
  );

  panorama.registerPanoProvider(getCustomPanorama);
}

// Return a pano image given the panoID.
function getCustomPanoramaTileUrl(
  pano: string,
  zoom: number,
  tileX: number,
  tileY: number
): string {
  return (
    "https://developers.google.com/maps/documentation/javascript/examples/full/images/" +
    "panoReception1024-" +
    zoom +
    "-" +
    tileX +
    "-" +
    tileY +
    ".jpg"
  );
}

// Construct the appropriate StreetViewPanoramaData given
// the passed pano IDs.
function getCustomPanorama(pano: string): google.maps.StreetViewPanoramaData {
  if (pano === "reception") {
    return {
      location: {
        pano: "reception",
        description: "Google Sydney - Reception",
      },
      links: [],
      // The text for the copyright control.
      copyright: "Imagery (c) 2010 Google",
      // The definition of the tiles for this panorama.
      tiles: {
        tileSize: new google.maps.Size(1024, 512),
        worldSize: new google.maps.Size(2048, 1024),
        // The heading in degrees at the origin of the panorama
        // tile set.
        centerHeading: 105,
        getTileUrl: getCustomPanoramaTileUrl,
      },
    };
  }
  // @ts-ignore TODO fix typings
  return null;
}

declare global {
  interface Window {
    initPano: () => void;
  }
}
window.initPano = initPano;
```

> [!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 initPano() {
  // Set up Street View and initially set it visible. Register the
  // custom panorama provider function. Set the StreetView to display
  // the custom panorama 'reception' which we check for below.
  const panorama = new google.maps.StreetViewPanorama(
    document.getElementById("map"),
    { pano: "reception", visible: true },
  );

  panorama.registerPanoProvider(getCustomPanorama);
}

// Return a pano image given the panoID.
function getCustomPanoramaTileUrl(pano, zoom, tileX, tileY) {
  return (
    "https://developers.google.com/maps/documentation/javascript/examples/full/images/" +
    "panoReception1024-" +
    zoom +
    "-" +
    tileX +
    "-" +
    tileY +
    ".jpg"
  );
}

// Construct the appropriate StreetViewPanoramaData given
// the passed pano IDs.
function getCustomPanorama(pano) {
  if (pano === "reception") {
    return {
      location: {
        pano: "reception",
        description: "Google Sydney - Reception",
      },
      links: [],
      // The text for the copyright control.
      copyright: "Imagery (c) 2010 Google",
      // The definition of the tiles for this panorama.
      tiles: {
        tileSize: new google.maps.Size(1024, 512),
        worldSize: new google.maps.Size(2048, 1024),
        // The heading in degrees at the origin of the panorama
        // tile set.
        centerHeading: 105,
        getTileUrl: getCustomPanoramaTileUrl,
      },
    };
  }
  // @ts-ignore TODO fix typings
  return null;
}

window.initPano = initPano;
```

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

### CSS

```css
/* 
 * Always set the map height explicitly to define the size of the div element
 * that contains the map. 
 */
#map {
  height: 100%;
}

/* 
 * Optional: Makes the sample page fill the window. 
 */
html,
body {
  height: 100%;
  margin: 0;
  padding: 0;
}
```

### HTML

```html
<html>
  <head>
    <title>Custom Street View Panoramas</title>

    <link rel="stylesheet" type="text/css" href="./style.css" />
    <script type="module" src="./index.js"></script>
  </head>
  <body>
    <div id="map"></div>

    <!-- 
      The `defer` attribute causes the script to execute after the full HTML
      document has been parsed. For non-blocking uses, avoiding race conditions,
      and consistent behavior across browsers, consider loading using Promises. See
      https://developers.google.com/maps/documentation/javascript/load-maps-js-api
      for more information.
      -->
    <script
      src="https://maps.googleapis.com/maps/api/js?key=AIzaSyB41DRUbKWJHPxaFjMAwdrzWzbVKartNGg&callback=initPano&v=weekly"
      defer
    ></script>
  </body>
</html>
```
[View example](https://developers.google.com/maps/documentation/javascript/examples/streetview-custom-simple.md.txt)

The custom panorama provider returns the appropriate tile given
the passed panorama ID, zoom level, and panorama tile coordinates.
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
`pano_zoom_tileX_tileY.png`.

The following example adds another arrow to the image, in addition to the
default Street View navigation arrows, that points into Google Sydney and links
to the custom imagery:

### TypeScript

```typescript
let panorama: google.maps.StreetViewPanorama;

// StreetViewPanoramaData of a panorama just outside the Google Sydney office.
let outsideGoogle: google.maps.StreetViewPanoramaData;

// StreetViewPanoramaData for a custom panorama: the Google Sydney reception.
function getReceptionPanoramaData(): google.maps.StreetViewPanoramaData {
  return {
    location: {
      pano: "reception", // The ID for this custom panorama.
      description: "Google Sydney - Reception",
      latLng: new google.maps.LatLng(-33.86684, 151.19583),
    },
    links: [
      {
        heading: 195,
        description: "Exit",
        pano: (outsideGoogle.location as google.maps.StreetViewLocation).pano,
      },
    ],
    copyright: "Imagery (c) 2010 Google",
    tiles: {
      tileSize: new google.maps.Size(1024, 512),
      worldSize: new google.maps.Size(2048, 1024),
      centerHeading: 105,
      getTileUrl: function (
        pano: string,
        zoom: number,
        tileX: number,
        tileY: number
      ): string {
        return (
          "https://developers.google.com/maps/documentation/javascript/examples/full/images/" +
          "panoReception1024-" +
          zoom +
          "-" +
          tileX +
          "-" +
          tileY +
          ".jpg"
        );
      },
    },
  };
}

function initPanorama() {
  panorama = new google.maps.StreetViewPanorama(
    document.getElementById("street-view") as HTMLElement,
    { pano: (outsideGoogle.location as google.maps.StreetViewLocation).pano }
  );
  // Register a provider for the custom panorama.
  panorama.registerPanoProvider(
    (pano: string): google.maps.StreetViewPanoramaData => {
      if (pano === "reception") {
        return getReceptionPanoramaData();
      }
      // @ts-ignore TODO fix typings
      return null;
    }
  );

  // Add a link to our custom panorama from outside the Google Sydney office.
  panorama.addListener("links_changed", () => {
    if (
      panorama.getPano() ===
      (outsideGoogle.location as google.maps.StreetViewLocation).pano
    ) {
      panorama.getLinks().push({
        description: "Google Sydney",
        heading: 25,
        pano: "reception",
      });
    }
  });
}

function initMap(): void {
  // Use the Street View service to find a pano ID on Pirrama Rd, outside the
  // Google office.
  new google.maps.StreetViewService()
    .getPanorama({ location: { lat: -33.867386, lng: 151.195767 } })
    .then(({ data }: google.maps.StreetViewResponse) => {
      outsideGoogle = data;
      initPanorama();
    });
}

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
let panorama;
// StreetViewPanoramaData of a panorama just outside the Google Sydney office.
let outsideGoogle;

// StreetViewPanoramaData for a custom panorama: the Google Sydney reception.
function getReceptionPanoramaData() {
  return {
    location: {
      pano: "reception", // The ID for this custom panorama.
      description: "Google Sydney - Reception",
      latLng: new google.maps.LatLng(-33.86684, 151.19583),
    },
    links: [
      {
        heading: 195,
        description: "Exit",
        pano: outsideGoogle.location.pano,
      },
    ],
    copyright: "Imagery (c) 2010 Google",
    tiles: {
      tileSize: new google.maps.Size(1024, 512),
      worldSize: new google.maps.Size(2048, 1024),
      centerHeading: 105,
      getTileUrl: function (pano, zoom, tileX, tileY) {
        return (
          "https://developers.google.com/maps/documentation/javascript/examples/full/images/" +
          "panoReception1024-" +
          zoom +
          "-" +
          tileX +
          "-" +
          tileY +
          ".jpg"
        );
      },
    },
  };
}

function initPanorama() {
  panorama = new google.maps.StreetViewPanorama(
    document.getElementById("street-view"),
    { pano: outsideGoogle.location.pano },
  );
  // Register a provider for the custom panorama.
  panorama.registerPanoProvider((pano) => {
    if (pano === "reception") {
      return getReceptionPanoramaData();
    }
    // @ts-ignore TODO fix typings
    return null;
  });
  // Add a link to our custom panorama from outside the Google Sydney office.
  panorama.addListener("links_changed", () => {
    if (panorama.getPano() === outsideGoogle.location.pano) {
      panorama.getLinks().push({
        description: "Google Sydney",
        heading: 25,
        pano: "reception",
      });
    }
  });
}

function initMap() {
  // Use the Street View service to find a pano ID on Pirrama Rd, outside the
  // Google office.
  new google.maps.StreetViewService()
    .getPanorama({ location: { lat: -33.867386, lng: 151.195767 } })
    .then(({ data }) => {
      outsideGoogle = data;
      initPanorama();
    });
}

window.initMap = initMap;
```

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

### CSS

```css
html,
body {
  height: 100%;
  margin: 0;
  padding: 0;
}

#street-view {
  height: 100%;
}
```

### HTML

```html
<html>
  <head>
    <title>Custom Street View Panorama Tiles</title>

    <link rel="stylesheet" type="text/css" href="./style.css" />
    <script type="module" src="./index.js"></script>
  </head>
  <body>
    <div id="street-view"></div>

    <!-- 
      The `defer` attribute causes the script to execute after the full HTML
      document has been parsed. For non-blocking uses, avoiding race conditions,
      and consistent behavior across browsers, consider loading using Promises. See
      https://developers.google.com/maps/documentation/javascript/load-maps-js-api
      for more information.
      -->
    <script
      src="https://maps.googleapis.com/maps/api/js?key=AIzaSyB41DRUbKWJHPxaFjMAwdrzWzbVKartNGg&callback=initMap&v=weekly"
      defer
    ></script>
  </body>
</html>
```
[View example](https://developers.google.com/maps/documentation/javascript/examples/streetview-custom-tiles.md.txt)