街景服务

使用集合让一切井井有条 根据您的偏好保存内容并对其进行分类。

概览

选择平台Android iOS JavaScript

Google 街景提供其整个覆盖区域内以指定道路为中心的 360 度全景视图。街景的 API 覆盖范围与 Google 地图应用 (https://maps.google.com/) 的覆盖范围相同。有关街景当前支持的城市列表,请访问 Google 地图网站

以下显示了一个示例街景图像。


Maps JavaScript API 提供用于获取和操纵 Google 地图街景中使用的图像的街景服务。此街景服务在浏览器中获得原生支持。

街景地图使用

尽管街景可以在独立的 DOM 元素中使用,但它在表示地图上的位置时最有用。默认情况下,地图上会启用街景,并且街景小人控件会集成显示到导航(缩放和平移)控件中。您可以通过将 streetViewControl 设置为 false,在地图的 MapOptions 中隐藏此控件。您还可以通过将 MapstreetViewControlOptions.position 属性设置为新的 ControlPosition 来更改街景控件的默认位置。

街景小人控件可让您直接在地图中查看街景全景图片。当用户点击并按住街景小人时,地图会更新,以显示支持街景的街道周围的蓝色轮廓,从而提供类似于 Google 地图应用的用户体验。

当用户将街景小人标记落到街道上时,地图会进行更新以显示指定位置的街景全景图片。

街景全景图片

支持使用 StreetViewPanorama 对象查看街景图像,该对象提供了适用于街景“查看者”的 API 接口。每张地图均包含一个默认的街景全景图片,您可以通过调用地图的 getStreetView() 方法进行检索。当您通过将地图的 streetViewControl 选项设置为 true 来向地图添加街景控件时,系统会自动将街景小人控件与此默认的街景全景图片相关联。

您也可以创建自己的 StreetViewPanorama 对象,并将地图的 streetView 属性设置为该构造对象,将地图设置为使用该对象而非默认属性。如果您想修改默认行为(例如,在地图和全景图片之间自动分享叠加层),则可能需要替换默认全景图片。(请参阅下文的街景中的叠加层)。

街景容器

您可能希望在单独的 DOM 元素(通常为 <div> 元素)中显示 StreetViewPanorama。只需在 StreetViewPanorama 的构造函数中传递 DOM 元素即可。为了获得最佳的图片显示效果,我们建议图片大小至少为 200 x 200 像素。

注意:街景功能旨在与地图结合使用,但这并非强制性要求。您可以使用没有地图的独立街景对象。

街景位置和视角 (POV)

StreetViewPanorama 构造函数还允许您使用 StreetViewOptions 参数设置街景位置和视点。您可以在构建后对该对象调用 setPosition()setPov(),以更改其位置和 POV。

街景位置定义了图片的相机焦点位置,但未为该图片定义相机方向。为此,StreetViewPov 对象定义了两个属性:

  • heading(默认为 0)以相对于正北的度数定义相机位置周围的旋转角度。朝向顺时针测量(90 度为正东)。
  • pitch(默认为 0)定义与相机初始默认间距的角度差异“向上”或“向下”,该值通常(但并不总是)水平的。(例如,在小山上拍摄的图像所呈现的默认间距可能并非水平间距。)仰视时测得的倾斜度角为正值(与默认倾斜度成 +90 度垂直向上正交),而俯视时测得的倾斜度角为 -90 度(与默认倾斜度成 -90 度垂直向下正交)。

StreetViewPov 对象最常用于确定街景相机的视点。您还可以使用 StreetViewPanorama.getPhotographerPov() 方法确定摄影师的视角,通常为汽车或三轮车面向的方向。

以下代码显示了一张波士顿地图,初始视角为芬威球场。选择街景小人并将其拖动到地图上的支持位置会更改街景全景图片:

TypeScript

function initialize() {
  const fenway = { lat: 42.345573, lng: -71.098326 };
  const map = new google.maps.Map(
    document.getElementById("map") as HTMLElement,
    {
      center: fenway,
      zoom: 14,
    }
  );
  const panorama = new google.maps.StreetViewPanorama(
    document.getElementById("pano") as HTMLElement,
    {
      position: fenway,
      pov: {
        heading: 34,
        pitch: 10,
      },
    }
  );

  map.setStreetView(panorama);
}

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

JavaScript

function initialize() {
  const fenway = { lat: 42.345573, lng: -71.098326 };
  const map = new google.maps.Map(document.getElementById("map"), {
    center: fenway,
    zoom: 14,
  });
  const panorama = new google.maps.StreetViewPanorama(
    document.getElementById("pano"),
    {
      position: fenway,
      pov: {
        heading: 34,
        pitch: 10,
      },
    }
  );

  map.setStreetView(panorama);
}

window.initialize = initialize;

CSS

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

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

HTML

<html>
  <head>
    <title>Street View split-map-panes</title>
    <script src="https://polyfill.io/v3/polyfill.min.js?features=default"></script>

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

    <!-- 
     The `defer` attribute causes the callback 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
     with https://www.npmjs.com/package/@googlemaps/js-api-loader.
    -->
    <script
      src="https://maps.googleapis.com/maps/api/js?key=AIzaSyB41DRUbKWJHPxaFjMAwdrzWzbVKartNGg&callback=initialize&v=weekly"
      defer
    ></script>
  </body>
</html>
查看示例

试用示例

移动设备上的运动跟踪功能

在支持设备屏幕方向事件的设备上,该 API 让用户能够根据设备的移动来更改街景视角。用户可通过移动设备进行环视。这称为运动跟踪或设备旋转跟踪。

作为应用开发者,您可以按如下方式更改默认行为:

  • 启用或停用运动跟踪功能。默认情况下,运动跟踪功能会在所有支持该功能的设备上启用。以下示例会停用运动跟踪,但会留下运动跟踪控件。 (请注意,用户可以通过点按该控件来启用运动跟踪。)
    var panorama = new google.maps.StreetViewPanorama(
        document.getElementById('pano'), {
          position: {lat: 37.869260, lng: -122.254811},
          pov: {heading: 165, pitch: 0},
          motionTracking: false
        });
    
  • 隐藏或显示运动跟踪控件。默认情况下,在支持运动跟踪的设备上会显示此控件。用户可以点按控件来开启或关闭运动跟踪功能。请注意,如果设备不支持运动跟踪,无论 motionTrackingControl 的值为何,该控件绝不会显示。

    以下示例会停用运动跟踪和运动跟踪控件。在这种情况下,用户无法开启运动跟踪功能:

    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
        });
    
  • 更改运动跟踪控件的默认位置。默认情况下,该控件会显示在全景图片右下角(位置 RIGHT_BOTTOM)附近。以下示例展示了如何将该控件的位置设为左下角:
    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
          }
        });
    

如需了解运动跟踪的实际应用,请查看移动设备(或任何支持设备屏幕方向事件的设备)中的以下示例:


查看示例

街景内的叠层

默认的 StreetViewPanorama 对象支持以原生方式显示地图叠加层。叠加层通常以锚定在“LatLng”位置的“街道级别”显示。(例如,标记将会带有尾部固定在街景全景图片中该位置的水平面)。

目前,街景全景图片支持的叠加层类型仅限于 MarkerInfoWindow 和自定义 OverlayView。您可以在地图上显示叠加层,方法是将街景视为 Map 对象的替代对象,调用 setMap() 并将 StreetViewPanorama 作为参数(而不是地图)进行传递。同样,调用 open() 并传递 StreetViewPanorama() 而非地图,即可在街景全景图片中打开信息窗口。

此外,创建使用默认 StreetViewPanorama 的地图时,在地图上创建的任何标记都会自动与地图关联的街景全景图片共享,前提是该全景图片可见。如需检索默认的街景全景图片,请对 Map 对象调用 getStreetView()。请注意,如果您将地图的 streetView 属性设置为您自己构造的 StreetViewPanorama,则会覆盖默认的全景图片。

以下示例显示了纽约市纽约市阿斯特广场周围各个位置的标记。将显示屏切换为街景,可显示 StreetViewPanorama 中显示的共享标记。

TypeScript

let panorama: google.maps.StreetViewPanorama;

function initMap(): void {
  const astorPlace = { lat: 40.729884, lng: -73.990988 };

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

  document
    .getElementById("toggle")!
    .addEventListener("click", toggleStreetView);

  // Set up the markers on the map
  const cafeMarker = new google.maps.Marker({
    position: { lat: 40.730031, lng: -73.991428 },
    map,
    icon: "https://chart.apis.google.com/chart?chst=d_map_pin_icon&chld=cafe|FFFF00",
    title: "Cafe",
  });

  const bankMarker = new google.maps.Marker({
    position: { lat: 40.729681, lng: -73.991138 },
    map,
    icon: "https://chart.apis.google.com/chart?chst=d_map_pin_icon&chld=dollar|FFFF00",
    title: "Bank",
  });

  const busMarker = new google.maps.Marker({
    position: { lat: 40.729559, lng: -73.990741 },
    map,
    icon: "https://chart.apis.google.com/chart?chst=d_map_pin_icon&chld=bus|FFFF00",
    title: "Bus Stop",
  });

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

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

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

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

JavaScript

let panorama;

function initMap() {
  const astorPlace = { lat: 40.729884, lng: -73.990988 };
  // Set up the map
  const map = new google.maps.Map(document.getElementById("map"), {
    center: astorPlace,
    zoom: 18,
    streetViewControl: false,
  });

  document.getElementById("toggle").addEventListener("click", toggleStreetView);

  // Set up the markers on the map
  const cafeMarker = new google.maps.Marker({
    position: { lat: 40.730031, lng: -73.991428 },
    map,
    icon: "https://chart.apis.google.com/chart?chst=d_map_pin_icon&chld=cafe|FFFF00",
    title: "Cafe",
  });
  const bankMarker = new google.maps.Marker({
    position: { lat: 40.729681, lng: -73.991138 },
    map,
    icon: "https://chart.apis.google.com/chart?chst=d_map_pin_icon&chld=dollar|FFFF00",
    title: "Bank",
  });
  const busMarker = new google.maps.Marker({
    position: { lat: 40.729559, lng: -73.990741 },
    map,
    icon: "https://chart.apis.google.com/chart?chst=d_map_pin_icon&chld=bus|FFFF00",
    title: "Bus Stop",
  });

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

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

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

window.initMap = initMap;

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

#floating-panel {
  margin-left: -100px;
}

HTML

<html>
  <head>
    <title>Overlays Within Street View</title>
    <script src="https://polyfill.io/v3/polyfill.min.js?features=default"></script>

    <link rel="stylesheet" type="text/css" href="./style.css" />
    <script type="module" src="./index.js"></script>
  </head>
  <body>
    <div id="floating-panel">
      <input type="button" value="Toggle Street View" id="toggle" />
    </div>
    <div id="map"></div>

    <!-- 
     The `defer` attribute causes the callback 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
     with https://www.npmjs.com/package/@googlemaps/js-api-loader.
    -->
    <script
      src="https://maps.googleapis.com/maps/api/js?key=AIzaSyB41DRUbKWJHPxaFjMAwdrzWzbVKartNGg&callback=initMap&v=weekly"
      defer
    ></script>
  </body>
</html>
查看示例

试用示例

街景事件

在街景之间导航或处理其屏幕方向时,您可能需要监控多个表示 StreetViewPanorama 状态更改的事件:

  • 每当单个全景图片 ID 发生变化时,就会触发 pano_changed。此事件并不能保证全景图片中的任何关联数据(如链接)也会在触发此事件时更改;此事件仅表示全景 ID 已更改。请注意,全景 ID(可用于引用此全景图片)仅在当前浏览器会话中是稳定的。
  • 每当全景的底层 (LatLng) 位置发生变化时,position_changed 就会触发。旋转全景不会触发此事件。请注意,您无需更改关联的全景图片 ID,即可更改全景图片的基础位置,因为 API 会自动将最近的全景图片 ID 与全景图片位置关联。
  • 每当街景的 StreetViewPov 发生变化时,pov_changed 就会触发。请注意,此事件可能会在位置和全景 ID 保持稳定时触发。
  • 每当街景的链接发生变化时,就会触发 links_changed。请注意,此事件可能会在通过 pano_changed 指示的全景图片 ID 发生更改后异步触发。
  • 只要街景的可见性发生变化,visible_changed 就会触发。请注意,此事件可能会在通过 pano_changed 指示的全景图片 ID 发生更改后异步触发。

以下代码说明了如何处理这些事件来收集有关底层 StreetViewPanorama 的数据:

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;

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;

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>
  <head>
    <title>Street View Events</title>
    <script src="https://polyfill.io/v3/polyfill.min.js?features=default"></script>

    <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 callback 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
     with https://www.npmjs.com/package/@googlemaps/js-api-loader.
    -->
    <script
      src="https://maps.googleapis.com/maps/api/js?key=AIzaSyB41DRUbKWJHPxaFjMAwdrzWzbVKartNGg&callback=initPano&v=weekly"
      defer
    ></script>
  </body>
</html>
查看示例

试用示例

街景控件

默认情况下,当显示 StreetViewPanorama 时,全景上会显示各种控件。您可以通过将 StreetViewPanoramaOptions 中的相应字段设置为 truefalse 来启用或停用这些控件:

  • panControl 提供了一种旋转全景的方法。默认情况下,此控件显示为标准集成罗盘和平移控件。您可以通过在 panControlOptions 字段中提供 PanControlOptions 来更改控件的位置。
  • zoomControl 提供了一种在图片内进行缩放的方法。该控件默认显示在全景图片右下角附近。您可以通过在 zoomControlOptions 字段中提供 ZoomControlOptions 来更改控件的外观。
  • addressControl 提供文本叠加层来指示关联营业地点的地址,并提供在 Google 地图中打开营业地点的链接。您可以通过在 addressControlOptions 字段中提供 StreetViewAddressControlOptions 来更改控件的外观。
  • fullscreenControl 提供了以全屏模式打开街景的选项。您可以通过在 fullscreenControlOptions 字段中提供 FullscreenControlOptions 来更改控件的外观。
  • motionTrackingControl 提供用于在移动设备上启用或停用运动跟踪的选项。此控件仅在支持设备屏幕方向事件的设备上显示。默认情况下,该控件会显示在全景图片右下角附近。您可以通过提供 MotionTrackingControlOptions 更改控件的位置。 如需了解详情,请参阅有关运动跟踪的部分。
  • linksControl 在图片上提供用于前往相邻全景图片的引导箭头。
  • “关闭”控件可让用户关闭街景查看器。您可以通过将 enableCloseButton 设置为 truefalse 来启用或停用关闭控件。

以下示例更改了关联街景中显示的控件,并移除了该视图的链接:

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;

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;

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>
  <head>
    <title>Street View Controls</title>
    <script src="https://polyfill.io/v3/polyfill.min.js?features=default"></script>

    <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 callback 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
     with https://www.npmjs.com/package/@googlemaps/js-api-loader.
    -->
    <script
      src="https://maps.googleapis.com/maps/api/js?key=AIzaSyB41DRUbKWJHPxaFjMAwdrzWzbVKartNGg&callback=initPano&v=weekly"
      defer
    ></script>
  </body>
</html>
查看示例

试用示例

直接访问街景数据

您可能希望以编程方式确定街景数据的可用性,或返回关于特定全景图片的信息,而无需直接操作地图/全景图片。为此,您可以使用 StreetViewService 对象,该对象为 Google 街景服务中存储的数据提供一个接口。

街景服务请求

访问街景服务是异步进行的,因为 Google Maps API 需要调用外部服务器。因此,您需要传递一个回调方法,以便在请求完成后执行。此回调方法将会对结果进行处理。

您可以使用 StreetViewPanoRequestStreetViewLocationRequestStreetViewService 发出请求。

使用 StreetViewPanoRequest 的请求会返回给定 ID 的全景数据,该 ID 用于唯一标识全景。请注意,这些参考 ID 只在该全景图片的所有生命周期内保持稳定。

使用 StreetViewLocationRequest 的请求会使用以下参数在指定位置搜索全景数据:

  • location 指定搜索全景图片的位置(纬度和经度)。
  • preference 用于设置可在半径范围内查找全景的偏好设置:最接近提供的营业地点的那个,或半径内效果最佳的营业地点。
  • radius 用于设置半径(以米为单位),用于搜索以给定纬度和经度为中心的全景图片。如果未提供,则默认为 50。
  • source 指定要搜索的全景图片的来源。有效值包括:
    • default 使用默认街景来源;搜索不限于特定来源。
    • outdoor 将搜索限制为户外集合。请注意,指定位置可能不存在室外全景图片。

街景服务响应

函数 getPanorama() 需要一个在从街景服务检索到结果后执行的回调函数。此回调函数会返回 StreetViewPanoramaData 对象中的一组全景图片数据,并依次指示指示请求状态的 StreetViewStatus 代码。

StreetViewPanoramaData 对象规范包含以下形式的关于街景全景图片的元数据:

{
  "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
  }
}

请注意,此数据对象本身不是 StreetViewPanorama 对象。如需使用此数据创建街景对象,您需要创建 StreetViewPanorama 并调用 setPano(),并向其传递返回的 location.pano 字段中所述的 ID。

status 代码可能会返回以下某个值:

  • OK 表示该服务找到了匹配的全景图片。
  • ZERO_RESULTS 表示该服务找不到与传递的标准相匹配的全景图片。
  • UNKNOWN_ERROR 表示无法处理街景请求,但具体原因未知。

以下代码会创建 StreetViewService,通过创建标记来响应用户点击地图,这些标记会在用户点击时显示该位置的 StreetViewPanorama。此代码使用该服务返回的 StreetViewPanoramaData 的内容。

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;

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;

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>
  <head>
    <title>Directly Accessing Street View Data</title>
    <script src="https://polyfill.io/v3/polyfill.min.js?features=default"></script>

    <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 callback 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
     with https://www.npmjs.com/package/@googlemaps/js-api-loader.
    -->
    <script
      src="https://maps.googleapis.com/maps/api/js?key=AIzaSyB41DRUbKWJHPxaFjMAwdrzWzbVKartNGg&callback=initMap&v=weekly"
      defer
    ></script>
  </body>
</html>
查看示例

试用示例

提供自定义街景全景图

Maps JavaScript API 支持在 StreetViewPanorama 对象中显示自定义全景图片。借助自定义全景图片,您可以显示建筑物的内部结构、风景名胜的美景,以及您想象中的任何景物。您甚至可以将这些自定义全景图片与 Google 现有的街景全景图片相关联。

设置一组自定义全景图片涉及以下步骤:

  • 为每幅自定义全景图创建一幅基本全景图像。此基础图片应采用您希望在放大后呈现的最高分辨率图片。
  • (可选,但建议进行)创建一组与基本图片具有不同缩放级别的全景图块。
  • 创建自定义全景图之间的链接。
  • (可选)在 Google 现有的街景图像内指定“进入”全景图片,并自定义指向/从自定义图片集指向标准图片集的链接。
  • StreetViewPanoramaData 对象中定义每张全景图片的元数据。
  • 实现一个用于确定自定义全景数据和图片的方法,并在 StreetViewPanorama 对象中将该方法指定为自定义处理程序。

下文对此过程做了说明。

创建自定义全景图

每张街景全景图片都是一个或一组图片,提供以单个位置为中心的 360 度全景视图。StreetViewPanorama 对象使用适合方形 (Plate Carrée) 投影的图片。此类投影包含 360 度水平视图(完整环绕一周)和 180 度垂直视图(从直上至直下)。这些视野会导致图片宽高比为 2:1。下面显示了完整的环绕式全景。

全景图片通常是通过以下方式获取的:从一个位置拍摄多张照片,然后使用全景软件将这些照片拼接到一起。(如需了解详情,请参阅 Wikipedia 的照片拼接应用的比较)。 此类图片应共用一个“相机”拍摄地点,拍摄所有的全景图片。然后,生成的 360 度全景图片可在球体上定义投影,并将图像包裹到球体的二维表面上。

将图片视为使用直线坐标系的球面投影时,可将图片分成直线图块,并根据计算的图块坐标提供图片,非常有益。

创建自定义全景图图块

街景还支持使用缩放控件实现不同的图片细节级别,该控件可让您通过默认视图进行缩放。通常,街景会为所有给定的全景图片提供 5 个级别的缩放级别。如果您所有的缩放级别都是由一幅全景图片提供的,那么这张图片要么过大,这样会显著降低您应用的速度;要么分辨率过低,这样在放大到一定程度时会产生糟糕的像素化图片。不过幸运的是,我们在提供不同缩放级别的 Google 地图图块时使用了类似的设计模式,因此,该模式可用于针对各缩放级别的全景提供适当的分辨率图像。

StreetViewPanorama 首次加载时,默认情况下其显示的图像包括缩放级别为 1 的全景图片的横向宽度的 25%(90 度圆弧)。此视图大致相当于正常的视野。缩小默认视图本质上会提供更宽的圆弧,而放大则会将视野缩小为较小的弧线。StreetViewPanorama 会自动计算适合所选缩放级别的视野,然后选择与该水平视野的尺寸大致匹配的图块组,从而选择最适合该分辨率的图像。以下视野已映射到街景缩放级别:

街景缩放级别 视野(度)
0 180
1(默认) 90
2 45
3 22.5
4 11.25

请注意,街景内显示的图片大小完全取决于街景容器的屏幕尺寸(宽度)。如果您提供更宽的容器,服务仍会为任何给定的缩放级别提供相同的视野,不过可能会改为选择更适合该分辨率的图块。

由于每张全景图片都由等距投影构成,因此创建全景图片图块相对比较简单。由于投影提供的图片宽高比为 2:1,因此宽高比为 2:1 的图块更易于使用,但方形图块可能会在方形地图上提供更好的性能(因为视野范围是正方形的)。

对于 2:1 图块,包含完整全景图像的单张图片代表缩放级别为 0 的完整全景“世界”(基础图片),缩放级别每增加 1 个,系统就会提供 4 个缩放级别图块。(例如,缩放级别为 2 时,完整的全景图片由 16 个图块组成。) 注意:街景图块处理所使用的缩放级别并不会直接与使用街景控件所提供的缩放级别相匹配;相反,街景控件缩放级别会选择一个视野 (FoV),然后系统会从中选择适合的图块。

通常,您会想要为图片图块命名,以便通过编程方式进行选择。下面的处理自定义全景图片请求一文对此类命名方案进行了介绍。

处理自定义全景图请求

若要使用自定义全景图片,请调用 StreetViewPanorama.registerPanoProvider(),并指定自定义全景图片提供程序方法的名称。全景图片提供程序方法必须返回 StreetViewPanoramaData 对象,并具有以下签名:

Function(pano):StreetViewPanoramaData

StreetViewPanoramaData 是采用以下形式的对象:

{
  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
  ]
}

按如下所示显示自定义全景图片:

注意:如果您希望显示自定义全景图片,请不要直接在 StreetViewPanorama 上设置 position,因为此类位置会指示街景服务请求靠近该位置的默认街景图像。请改为在自定义 StreetViewPanoramaData 对象的 location.latLng 字段中设置此位置。

以下示例显示了 Google 悉尼办事处的自定义全景图片。请注意,此示例并未使用地图或默认街景图像:

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;

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;

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>
  <head>
    <title>Custom Street View Panoramas</title>
    <script src="https://polyfill.io/v3/polyfill.min.js?features=default"></script>

    <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 callback 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
     with https://www.npmjs.com/package/@googlemaps/js-api-loader.
    -->
    <script
      src="https://maps.googleapis.com/maps/api/js?key=AIzaSyB41DRUbKWJHPxaFjMAwdrzWzbVKartNGg&callback=initPano&v=weekly"
      defer
    ></script>
  </body>
</html>
查看示例

试用示例

自定义的全景图片提供程序会根据传递的全景图片 ID、缩放级别和全景图片图块坐标返回适当的图块。由于图片选择取决于这些传递的值,因此在给定这些传递的值(例如 pano_zoom_tileX_tileY.png)的情况下,可以通过编程方式选择图片。

以下示例除默认街景导航箭头外,还向图片添加了另一个箭头,该箭头指向 Google 悉尼分公司并指向自定义图像的链接:

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;

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",
      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;

CSS

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

#street-view {
  height: 100%;
}

HTML

<html>
  <head>
    <title>Custom Street View Panorama Tiles</title>
    <script src="https://polyfill.io/v3/polyfill.min.js?features=default"></script>

    <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 callback 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
     with https://www.npmjs.com/package/@googlemaps/js-api-loader.
    -->
    <script
      src="https://maps.googleapis.com/maps/api/js?key=AIzaSyB41DRUbKWJHPxaFjMAwdrzWzbVKartNGg&callback=initMap&v=weekly"
      defer
    ></script>
  </body>
</html>
查看示例

试用示例