街景服務

總覽

選取平台: Android iOS JavaScript

Google 街景服務可讓您在指定道路的涵蓋區域進行 360 度全景檢視。街景服務的 API 涵蓋範圍與 Google 地圖應用程式 (https://maps.google.com/) 相同。如需街景服務目前支援的城市清單,請參閱 Google 地圖網站

以下是街景服務圖片的範例。


Maps JavaScript API 提供街景服務,以便取得及處理在 Google 地圖街景服務中使用的圖像。瀏覽器原生支援這項街景服務。

街景服務地圖的用法

雖然街景服務可以在獨立的 DOM 元素內使用,但最大的用處是指出地圖上的地點。根據預設,地圖會啟用街景服務,並將街景服務的「衣夾人控制項」整合在導覽 (縮放及平移) 控制項中。您可以在地圖的 MapOptions 內將 streetViewControl 設為 false,以隱藏這個控制項。此外,您也可以將 MapstreetViewControlOptions.position 屬性設為新的 ControlPosition,以變更街景服務控制項的預設位置。

您可以使用「街景服務衣夾人」控制項直接在地圖內查看街景服務全景。使用者點選並按住衣夾人後,地圖會進行更新,在啟用街景服務的街道周圍顯示藍色外框,提供與 Google 地圖應用程式相似的使用者體驗。

使用者將「衣夾人」標記拖放到街道上時,地圖會進行更新,顯示指定地點的街景服務全景。

街景服務全景

StreetViewPanorama 物件會為街景服務「檢視器」提供 API 介面,可用來支援街景服務圖片。每張地圖都包含預設的街景服務全景,您可以呼叫地圖的 getStreetView() 方法來擷取全景。如果您為了在地圖中加入「街景服務」控制項,而將 streetViewControl 選項設為 true,系統就會自動將「衣夾人」控制項連結到這個預設的街景服務全景。

此外,如要自行建立 StreetViewPanorama 物件,並將地圖設為使用這個物件而非預設值,您也可以將地圖的 streetView 屬性明確設為該建構物件。如要修改預設行為 (例如在地圖與全景之間自動共用疊加層),建議您覆寫預設全景 (請參閱下方的「街景服務內的疊加層」一節)。

街景服務容器

您可能會想改為在獨立的 DOM 元素 (通常是 <div> 元素) 內顯示 StreetViewPanorama。方法很簡單,只要在 StreetViewPanorama 的建構函式中傳遞 DOM 元素即可。為求最佳顯示效果,建議圖片尺寸至少為 200 x 200 像素。

注意:街景服務功能是設計用來搭配地圖,但這並非硬性規定。即使沒有地圖,您也可以單獨使用街景服務物件。

街景服務位置和視角

StreetViewPanorama 建構函式也可讓您使用 StreetViewOptions 參數來設定街景服務位置和視角。建構完成後,您可以針對物件呼叫 setPosition()setPov() 來變更位置和視角。

街景服務位置會定義圖片中相機焦點的位置,但不會定義圖片中相機的方向。為了解決這個問題,StreetViewPov 物件會定義兩個屬性:

  • heading (預設為 0) 以正北為基準,定義相機擺放位置的旋轉角度 (以度為單位)。方向依順時針測量 (90 度為正東方)。
  • pitch (預設為 0) 定義相機初始預設傾斜角度的「向上」或「向下」變異量,預設傾斜角度通常 (但不一定) 是水平 (例如,在山上拍攝的圖片中,預設傾斜角度可能就不是水平)。傾斜角度的測量方式為正值朝上 (垂直向上 +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 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=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);

  const cafeIcon = document.createElement("img");
  cafeIcon.src = "https://developers.google.com/maps/documentation/javascript/examples/full/images/cafe_icon.svg";

  const dollarIcon = document.createElement("img");
  dollarIcon.src = "https://developers.google.com/maps/documentation/javascript/examples/full/images/bank_icon.svg";

  const busIcon = document.createElement("img");
  busIcon.src = "https://developers.google.com/maps/documentation/javascript/examples/full/images/bus_icon.svg";


  // Set up the markers on the map
  const cafeMarker = new google.maps.Marker({
    position: { lat: 40.730031, lng: -73.991428 },
    map,
    title: "Cafe",
    icon: cafeIcon.src,
  });

  const bankMarker = new google.maps.Marker({
    position: { lat: 40.729681, lng: -73.991138 },
    map,
    title: "Bank",
    icon: dollarIcon.src,
  });

  const busMarker = new google.maps.Marker({
    position: { lat: 40.729559, lng: -73.990741 },
    map,
    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 = 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);

  const cafeIcon = document.createElement("img");

  cafeIcon.src =
    "https://developers.google.com/maps/documentation/javascript/examples/full/images/cafe_icon.svg";

  const dollarIcon = document.createElement("img");

  dollarIcon.src =
    "https://developers.google.com/maps/documentation/javascript/examples/full/images/bank_icon.svg";

  const busIcon = document.createElement("img");

  busIcon.src =
    "https://developers.google.com/maps/documentation/javascript/examples/full/images/bus_icon.svg";

  // Set up the markers on the map
  const cafeMarker = new google.maps.Marker({
    position: { lat: 40.730031, lng: -73.991428 },
    map,
    title: "Cafe",
    icon: cafeIcon.src,
  });
  const bankMarker = new google.maps.Marker({
    position: { lat: 40.729681, lng: -73.991138 },
    map,
    title: "Bank",
    icon: dollarIcon.src,
  });
  const busMarker = new google.maps.Marker({
    position: { lat: 40.729559, lng: -73.990741 },
    map,
    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 = 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 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&libraries=marker&v=weekly"
      defer
    ></script>
  </body>
</html>
查看範例

測試範例程式碼

街景服務事件

瀏覽街景服務或控制方向時,請留意幾個代表 StreetViewPanorama 狀態出現變化的事件:

  • pano_changed,每當個別全景 ID 變更時就會觸發。這個事件並不表示在事件觸發時,全景內的任何相關聯資料 (例如連結) 也隨之變更,只是代表全景 ID 有所變更。請注意,全景 ID (可做為這個全景的參照) 僅在目前瀏覽器工作階段內保持不變。
  • position_changed,每當全景的基礎 (LatLng) 位置變更時就會觸發。旋轉全景不會觸發這個事件。請注意,您可以在不變更相關聯全景 ID 的情況下,調整全景的基礎位置,因為 API 會自動將最靠近的全景 ID 與全景位置建立關聯。
  • pov_changed,每當街景服務的 StreetViewPov 變更時就會觸發。請注意,位置和全景 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 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>
查看範例

測試範例程式碼

街景服務控制項

根據預設,顯示 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 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>
查看範例

測試範例程式碼

直接存取街景服務資料

您可能會想透過程式輔助方式判斷是否有街景服務資料,或是傳回特定全景的相關資訊,而不需要直接處理地圖/全景。方法很簡單,您可以使用 StreetViewService 物件,為 Google 街景服務中儲存的資料提供介面。

街景服務要求

Google Maps API 必須呼叫外部伺服器,因此街景服務是以非同步的方式存取。基於這個理由,您必須傳遞在完成要求後執行的「回呼」方法。這個回呼方法會處理結果。

您可以使用 StreetViewPanoRequestStreetViewLocationRequestStreetViewService 提出要求。

使用 StreetViewPanoRequest 的要求會傳回全景資料,其中包含用於識別全景的參照 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 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>
查看範例

測試範例程式碼

提供自訂街景服務全景

Maps JavaScript API 支援在 StreetViewPanorama 物件內顯示自訂全景。自訂全景可用於顯示建築物內部、景點風光,或是您想像的任何內容。您甚至可以將這些自訂全景全都連結至 Google 現有的街景服務全景。

如要自訂一組全景圖,請按照下列步驟操作:

  • 為每個自訂全景建立基本全景圖片。這張基本圖片必須是最高解析度的圖片,以便提供放大圖像。
  • (建議選用) 運用基本圖片建立一組包含各種縮放等級的全景圖塊。
  • 連結不同的自訂全景。
  • (選用) 在 Google 現有的街景服務圖像內指定「基本」全景,然後自行調整自訂組合與標準組合之間的連結。
  • StreetViewPanoramaData 物件內的每張全景圖片定義中繼資料。
  • 導入可以判定自訂全景資料和圖片的方法,並將該方法指定為您在 StreetViewPanorama 物件內的自訂處理常式。

以下章節將說明這個程序。

建立自訂全景

每個街景服務全景都由一或多張圖片組成,提供單一位置的 360 度完整環景。StreetViewPanorama 物件會使用與等距長方投影一致的圖片。這種投影包含 360 度的水平視圖 (全環景) 與 180 度的垂直視圖 (由正上方到正下方)。這樣的視野可以產生顯示比例為 2:1 的圖片。全環景如下所示:

城市街道的全景

全景圖片一般是先在同一個位置拍攝多張相片,再使用全景軟體拼接而成 (詳情請參閱維基百科的「相片拼接應用程式的比較」一文)。這類圖片必須採用相同的「相機」擺放位置,而每張全景圖片都會從這個擺放位置拍攝。接著,產生的 360 度全景便可定義球體投影,在球體的 2D 表面上呈現圖片。

顯示街道全景的球體

將圖片分割為直線式「圖塊」,並依據計算出的圖塊座標提供圖片時,建議您將全景視為使用直線座標系統在球體上做出的投影。

建立自訂全景圖塊

街景服務也支援各種圖片精細度,只要使用縮放控制項,就能縮小或放大預設檢視畫面。一般來說,不論是哪張全景圖片,街景服務都提供五種縮放等級解析度。如果您想要運用一張全景圖片提供所有縮放等級,這類圖片可能會十分龐大,大幅減緩應用程式速度,或是在高縮放等級下的解析度不佳,只能提供低像素圖片。不過,我們可以採取類似於提供不同縮放等級的 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 的整個全景「世界」(基本圖片),且每增加一個縮放等級就會顯示 4 個 zoomLevel 圖塊 (舉例來說,縮放等級為 2 時,整個全景會包含 16 個圖塊)。注意:街景服務圖塊的縮放等級,不會直接與使用街景服務控制項時所提供的縮放等級相符。街景服務控制項縮放等級會改為選取視野,然後再依據視野選取適當的圖塊。

分割為圖塊的城市街道全景

一般來說,您可以為圖片中的圖塊命名,以便透過程式輔助方式選取圖塊。我們會在下方的「處理自訂全景要求」一節中說明這類命名機制。

處理自訂全景要求

如要使用自訂全景,請呼叫 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 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>
查看範例

測試範例程式碼

自訂全景提供者會根據傳遞的全景 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", // 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;

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 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>
查看範例

測試範例程式碼