您已全部設定完成!

若要開始開發,請參閱我們的開發人員文件

啟用 Google Maps JavaScript API

為協助您開始,我們將先引導您使用「Google 開發人員控制台」來執行一些動作:

  1. 建立或選擇專案
  2. 啟用 Google Maps JavaScript API 與相關服務
  3. 建立適當的金鑰
繼續

使用 Firebase 進行協力即時地圖搜尋

總覽

此教學課程說明如何使用 Firebase 應用程式平台建立互動式地圖。嘗試在地圖上按一下下面的不同位置,以建置熱度圖。

下一節顯示建立此教學課程所述之地圖所需的完整程式碼。

/**
* Reference to Firebase database.
* @const
*/
var firebase = new Firebase('https://fire-map-tutorial.firebaseio.com/');

/**
* Data object to be written to Firebase.
*/
var data = {
  sender: null,
  timestamp: null,
  lat: null,
  lng: null
};

function makeInfoBox(controlDiv, map) {
  // Set CSS for the control border.
  var controlUI = document.createElement('div');
  controlUI.style.boxShadow = 'rgba(0, 0, 0, 0.298039) 0px 1px 4px -1px';
  controlUI.style.backgroundColor = '#fff';
  controlUI.style.border = '2px solid #fff';
  controlUI.style.borderRadius = '2px';
  controlUI.style.marginBottom = '22px';
  controlUI.style.marginTop = '10px';
  controlUI.style.textAlign = 'center';
  controlDiv.appendChild(controlUI);

  // Set CSS for the control interior.
  var controlText = document.createElement('div');
  controlText.style.color = 'rgb(25,25,25)';
  controlText.style.fontFamily = 'Roboto,Arial,sans-serif';
  controlText.style.fontSize = '100%';
  controlText.style.padding = '6px';
  controlText.textContent = 'The map shows all clicks made in the last 10 minutes.';
  controlUI.appendChild(controlText);
}

/**
* Starting point for running the program. Authenticates the user.
* @param {function()} onAuthSuccess - Called when authentication succeeds.
*/
function initAuthentication(onAuthSuccess) {
  firebase.authAnonymously(function(error, authData) {
    if (error) {
      console.log('Login Failed!', error);
    } else {
      data.sender = authData.uid;
      onAuthSuccess();
    }
  }, {remember: 'sessionOnly'});  // Users will get a new id for every session.
}

/**
 * Creates a map object with a click listener and a heatmap.
 */
function initMap() {
  var map = new google.maps.Map(document.getElementById('map'), {
    center: {lat: 0, lng: 0},
    zoom: 3,
    styles: [{
      featureType: 'poi',
      stylers: [{ visibility: 'off' }]  // Turn off POI.
    },
    {
      featureType: 'transit.station',
      stylers: [{ visibility: 'off' }]  // Turn off bus, train stations etc.
    }],
    disableDoubleClickZoom: true,
    streetViewControl: false,
  });

  // Create the DIV to hold the control and call the makeInfoBox() constructor
  // passing in this DIV.
  var infoBoxDiv = document.createElement('div');
  makeInfoBox(infoBoxDiv, map);
  map.controls[google.maps.ControlPosition.TOP_CENTER].push(infoBoxDiv);

  // Listen for clicks and add the location of the click to firebase.
  map.addListener('click', function(e) {
    data.lat = e.latLng.lat();
    data.lng = e.latLng.lng();
    addToFirebase(data);
  });

  // Create a heatmap.
  var heatmap = new google.maps.visualization.HeatmapLayer({
    data: [],
    map: map,
    radius: 16
  });

  initAuthentication(initFirebase.bind(undefined, heatmap));
}

/**
 * Set up a Firebase with deletion on clicks older than expirySeconds
 * @param {!google.maps.visualization.HeatmapLayer} heatmap The heatmap to
 * which points are added from Firebase.
 */
function initFirebase(heatmap) {

  // 10 minutes before current time.
  var startTime = new Date().getTime() - (60 * 10 * 1000);

  // Reference to the clicks in Firebase.
  var clicks = firebase.child('clicks');

  // Listener for when a click is added.
  clicks.orderByChild('timestamp').startAt(startTime).on('child_added',
    function(snapshot) {

      // Get that click from firebase.
      var newPosition = snapshot.val();
      var point = new google.maps.LatLng(newPosition.lat, newPosition.lng);
      var elapsed = new Date().getTime() - newPosition.timestamp;

      // Add the point to  the heatmap.
      heatmap.getData().push(point);

      // Requests entries older than expiry time (10 minutes).
      var expirySeconds = Math.max(60 * 10 * 1000 - elapsed, 0);
      // Set client timeout to remove the point after a certain time.
      window.setTimeout(function() {
        // Delete the old point from the database.
        snapshot.ref().remove();
      }, expirySeconds);
    }
  );

  // Remove old data from the heatmap when a point is removed from firebase.
  clicks.on('child_removed', function(snapshot, prevChildKey) {
    var heatmapData = heatmap.getData();
    var i = 0;
    while (snapshot.val().lat != heatmapData.getAt(i).lat()
      || snapshot.val().lng != heatmapData.getAt(i).lng()) {
      i++;
    }
    heatmapData.removeAt(i);
  });
}

/**
 * Updates the last_message/ path with the current timestamp.
 * @param {function(Date)} addClick After the last message timestamp has been updated,
 *     this function is called with the current timestamp to add the
 *     click to the firebase.
 */
function getTimestamp(addClick) {
  // Reference to location for saving the last click time.
  var ref = firebase.child('last_message/' + data.sender);

  ref.onDisconnect().remove();  // Delete reference from firebase on disconnect.

  // Set value to timestamp.
  ref.set(Firebase.ServerValue.TIMESTAMP, function(err) {
    if (err) {  // Write to last message was unsuccessful.
      console.log(err);
    } else {  // Write to last message was successful.
      ref.once('value', function(snap) {
        addClick(snap.val());  // Add click with same timestamp.
      }, function(err) {
        console.warn(err);
      });
    }
  });
}

/**
 * Adds a click to firebase.
 * @param {Object} data The data to be added to firebase.
 *     It contains the lat, lng, sender and timestamp.
 */
function addToFirebase(data) {
  getTimestamp(function(timestamp) {
    // Add the new timestamp to the record data.
    data.timestamp = timestamp;
    var ref = firebase.child('clicks').push(data, function(err) {
      if (err) {  // Data was not written to firebase.
        console.warn(err);
      }
    });
  });
}
<div id="map"></div>
/* 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;
}
<!-- Replace the value of the key parameter with your own API key. -->
<script src="https://maps.googleapis.com/maps/api/js?key=AIzaSyCkUOdZ5y7hMm0yrcCQoCvLwzdM6M8s5qk&libraries=visualization&callback=initMap" async defer></script>
<script src="https://cdn.firebase.com/js/client/2.3.2/firebase.js"></script>

自己試試看

將游標放在程式碼區塊右上方,以複製程式碼或在 JSFiddle 中將它開啟。

<!DOCTYPE html>
<html>
  <head>
    <style>
      /* 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;
      }
    </style>
  </head>
  <body>
    <div id="map"></div>

    <script src="https://cdn.firebase.com/js/client/2.3.2/firebase.js"></script>
    <script>
      /**
      * Reference to Firebase database.
      * @const
      */
      var firebase = new Firebase('https://fire-map-tutorial.firebaseio.com/');

      /**
      * Data object to be written to Firebase.
      */
      var data = {
        sender: null,
        timestamp: null,
        lat: null,
        lng: null
      };

      function makeInfoBox(controlDiv, map) {
        // Set CSS for the control border.
        var controlUI = document.createElement('div');
        controlUI.style.boxShadow = 'rgba(0, 0, 0, 0.298039) 0px 1px 4px -1px';
        controlUI.style.backgroundColor = '#fff';
        controlUI.style.border = '2px solid #fff';
        controlUI.style.borderRadius = '2px';
        controlUI.style.marginBottom = '22px';
        controlUI.style.marginTop = '10px';
        controlUI.style.textAlign = 'center';
        controlDiv.appendChild(controlUI);

        // Set CSS for the control interior.
        var controlText = document.createElement('div');
        controlText.style.color = 'rgb(25,25,25)';
        controlText.style.fontFamily = 'Roboto,Arial,sans-serif';
        controlText.style.fontSize = '100%';
        controlText.style.padding = '6px';
        controlText.textContent = 'The map shows all clicks made in the last 10 minutes.';
        controlUI.appendChild(controlText);
      }

      /**
      * Starting point for running the program. Authenticates the user.
      * @param {function()} onAuthSuccess - Called when authentication succeeds.
      */
      function initAuthentication(onAuthSuccess) {
        firebase.authAnonymously(function(error, authData) {
          if (error) {
            console.log('Login Failed!', error);
          } else {
            data.sender = authData.uid;
            onAuthSuccess();
          }
        }, {remember: 'sessionOnly'});  // Users will get a new id for every session.
      }

      /**
       * Creates a map object with a click listener and a heatmap.
       */
      function initMap() {
        var map = new google.maps.Map(document.getElementById('map'), {
          center: {lat: 0, lng: 0},
          zoom: 3,
          styles: [{
            featureType: 'poi',
            stylers: [{ visibility: 'off' }]  // Turn off POI.
          },
          {
            featureType: 'transit.station',
            stylers: [{ visibility: 'off' }]  // Turn off bus, train stations etc.
          }],
          disableDoubleClickZoom: true,
          streetViewControl: false,
        });

        // Create the DIV to hold the control and call the makeInfoBox() constructor
        // passing in this DIV.
        var infoBoxDiv = document.createElement('div');
        makeInfoBox(infoBoxDiv, map);
        map.controls[google.maps.ControlPosition.TOP_CENTER].push(infoBoxDiv);

        // Listen for clicks and add the location of the click to firebase.
        map.addListener('click', function(e) {
          data.lat = e.latLng.lat();
          data.lng = e.latLng.lng();
          addToFirebase(data);
        });

        // Create a heatmap.
        var heatmap = new google.maps.visualization.HeatmapLayer({
          data: [],
          map: map,
          radius: 16
        });

        initAuthentication(initFirebase.bind(undefined, heatmap));
      }

      /**
       * Set up a Firebase with deletion on clicks older than expirySeconds
       * @param {!google.maps.visualization.HeatmapLayer} heatmap The heatmap to
       * which points are added from Firebase.
       */
      function initFirebase(heatmap) {

        // 10 minutes before current time.
        var startTime = new Date().getTime() - (60 * 10 * 1000);

        // Reference to the clicks in Firebase.
        var clicks = firebase.child('clicks');

        // Listener for when a click is added.
        clicks.orderByChild('timestamp').startAt(startTime).on('child_added',
          function(snapshot) {

            // Get that click from firebase.
            var newPosition = snapshot.val();
            var point = new google.maps.LatLng(newPosition.lat, newPosition.lng);
            var elapsed = new Date().getTime() - newPosition.timestamp;

            // Add the point to  the heatmap.
            heatmap.getData().push(point);

            // Requests entries older than expiry time (10 minutes).
            var expirySeconds = Math.max(60 * 10 * 1000 - elapsed, 0);
            // Set client timeout to remove the point after a certain time.
            window.setTimeout(function() {
              // Delete the old point from the database.
              snapshot.ref().remove();
            }, expirySeconds);
          }
        );

        // Remove old data from the heatmap when a point is removed from firebase.
        clicks.on('child_removed', function(snapshot, prevChildKey) {
          var heatmapData = heatmap.getData();
          var i = 0;
          while (snapshot.val().lat != heatmapData.getAt(i).lat()
            || snapshot.val().lng != heatmapData.getAt(i).lng()) {
            i++;
          }
          heatmapData.removeAt(i);
        });
      }

      /**
       * Updates the last_message/ path with the current timestamp.
       * @param {function(Date)} addClick After the last message timestamp has been updated,
       *     this function is called with the current timestamp to add the
       *     click to the firebase.
       */
      function getTimestamp(addClick) {
        // Reference to location for saving the last click time.
        var ref = firebase.child('last_message/' + data.sender);

        ref.onDisconnect().remove();  // Delete reference from firebase on disconnect.

        // Set value to timestamp.
        ref.set(Firebase.ServerValue.TIMESTAMP, function(err) {
          if (err) {  // Write to last message was unsuccessful.
            console.log(err);
          } else {  // Write to last message was successful.
            ref.once('value', function(snap) {
              addClick(snap.val());  // Add click with same timestamp.
            }, function(err) {
              console.warn(err);
            });
          }
        });
      }

      /**
       * Adds a click to firebase.
       * @param {Object} data The data to be added to firebase.
       *     It contains the lat, lng, sender and timestamp.
       */
      function addToFirebase(data) {
        getTimestamp(function(timestamp) {
          // Add the new timestamp to the record data.
          data.timestamp = timestamp;
          var ref = firebase.child('clicks').push(data, function(err) {
            if (err) {  // Data was not written to firebase.
              console.warn(err);
            }
          });
        });
      }
    </script>
    <script async defer
        src="https://maps.googleapis.com/maps/api/js?key=YOUR_API_KEY&libraries=visualization&callback=initMap">
    </script>
  </body>
</html>

開始使用

您可以使用此教學課程中的程式碼來開發自己的 Firebase 地圖版本。若要開始這樣做,請在文字編輯器中建立新檔案,並將它儲存為 index.html

閱讀後續各節,以了解您可以新增到此檔案中的程式碼。

建立基本地圖

此節說明用於建立基本地圖的程式碼。這可能與您在開始使用 Google Maps JavaScript API 中建立地圖的方式類似。

將下列程式碼複製到您的 index.html 檔案中。此程式碼會載入 Google Maps JavaScript API,並將地圖設定為全螢幕。它也會載入視覺程式庫,您稍後將在教學課程中使用它來建立熱度圖。

<!DOCTYPE html>
<html>
  <head>
    <style type="text/css">
      #map {
        height: 100%;
      }
      html, body {
        height: 100%;
        margin: 0;
        padding: 0;
      }
    </style>
  </head>
  <body>
    <div id="map"></div>
    <script async defer
        src="https://maps.googleapis.com/maps/api/js?key=YOUR_API_KEY
        &libraries=visualization&callback=initMap">
    </script>

    /**
     * The JavaScript code that creates the firebase map goes between the empty script tags below.
     */
    <script>firebasemap.js</script>

  </body>
</html>

按一下程式碼範例中的 YOUR_API_KEY,或依照操作說明取得 API 金鑰。使用您應用程式的 API 金鑰來取代 YOUR_API_KEY


後續各節說明用於建立 Firebase 地圖的 JavaScript 程式碼。您可以複製程式碼並將它儲存在 firebasemap.js 檔案中,並在 script 標記之間參考,如下所示。

<script>firebasemap.js</script>
或者,您可以將程式碼直接插入到 script 標記內,如此教學課程開頭的完整範例程式碼中所述。

將下面的程式碼新增到 firebasemap.js 檔案,或新增到 index.html 檔案的空白 script 標記之間。這是透過建立初始化地圖物件函式執行程式的起始點。

function initMap() {
  var map = new google.maps.Map(document.getElementById('map'), {
    center: {lat: 0, lng: 0},
    zoom: 3,
    styles: [{
      featureType: 'poi',
      stylers: [{ visibility: 'off' }]  // Turn off points of interest.
    }, {
      featureType: 'transit.station',
      stylers: [{ visibility: 'off' }]  // Turn off bus stations, train stations, etc.
    }],
    disableDoubleClickZoom: true
  });
}

為讓此可點擊的熱度圖更容易使用,上面的程式碼已停用搜尋點與轉運站(在點擊時會顯示資訊視窗)。它也會透過按兩下停用縮放以防止意外縮放。請閱讀文件以取得更多地圖功能資訊

完全載入 API 之後,下面指令碼標記中的回呼參數會執行 HTML 檔案中的 initMap() 函式。

<script async defer
    src="https://maps.googleapis.com/maps/api/js?key=YOUR_API_KEY
    &libraries=visualization&callback=initMap">

新增下面的程式碼以在地圖上方建立文字控制項。

  function makeInfoBox(controlDiv, map) {
    // Set CSS for the control border.
    var controlUI = document.createElement('div');
    controlUI.style.boxShadow = 'rgba(0, 0, 0, 0.298039) 0px 1px 4px -1px';
    controlUI.style.backgroundColor = '#fff';
    controlUI.style.border = '2px solid #fff';
    controlUI.style.borderRadius = '2px';
    controlUI.style.marginBottom = '22px';
    controlUI.style.marginTop = '10px';
    controlUI.style.textAlign = 'center';
    controlDiv.appendChild(controlUI);

    // Set CSS for the control interior.
    var controlText = document.createElement('div');
    controlText.style.color = 'rgb(25,25,25)';
    controlText.style.fontFamily = 'Roboto,Arial,sans-serif';
    controlText.style.fontSize = '100%';
    controlText.style.padding = '6px';
    controlText.innerText = 'The map shows all clicks made in the last 10 minutes.';
    controlUI.appendChild(controlText);
  }

initMap 函式內的 var map 後方新增下面的程式碼,以載入文字控制項方塊。

  // Create the DIV to hold the control and call the makeInfoBox() constructor
  // passing in this DIV.
  var infoBoxDiv = document.createElement('div');
  var infoBox = new makeInfoBox(infoBoxDiv, map);
  infoBoxDiv.index = 1;
  map.controls[google.maps.ControlPosition.TOP_CENTER].push(infoBoxDiv);
立即嘗試

若要檢視程式碼所建立的 Google 地圖,請在網頁瀏覽器中開啟 index.html

設定 Firebase

若要讓此應用程式支援協力處理功能,您必須將點擊儲存在能由所有使用者存取的外部資料庫中。「Firebase 即時資料庫」符合此用途,而且不需要具備任何 SQL 相關知識即可使用。

首先,註冊以取得免費 Firebase 帳戶。若您是第一次使用 Firebase,您將會看到名為 "My First App" (我的第一個應用程式)的新應用程式。若建立新應用程式,您可以為它賦予新名稱以及結尾為 firebaseIO.com 的自訂 Firebase 網址。例如,您可以將應用程式命名為 "Jane's Firebase Map" 並指定 https://janes-firebase-map.firebaseIO.com 的網址。您可以使用此網址將資料庫連結到您的 JavaScript 應用程式。

將下面一行新增到您 HTML 檔案的 <head> 標記後方,以匯入 Firebase 程式庫。

<script src="https://cdn.firebase.com/js/client/2.3.2/firebase.js"></script>

新增下一行到您的 JavaScript 檔案中:

var firebase = new Firebase("<Your Firebase URL here>");

在 Firebase 中儲存點擊資料

此節說明用滑鼠在地圖上點擊在 Firebase 中儲存資料的程式碼。

對於地圖上的每個滑鼠點擊,下面的程式碼會建立全域資料物件並將其資訊儲存在 Firebase 中。此物件會記錄資料,例如其 latLng、點擊的時間戳記,以及建立點擊之瀏覽器的唯一 ID。

/**
 * Data object to be written to Firebase.
 */
var data = {
  sender: null,
  timestamp: null,
  lat: null,
  lng: null
};

下面的程式碼會記錄每個點擊的唯一工作階段 ID,這有助於控制地圖上的流量速率,以符合 Firebase 安全規則。

/**
 * Starting point for running the program. Authenticates the user.
 * @param {function} Called when authentication succeeds.
 */
function initAuthentication(onAuthSuccess) {
  firebase.authAnonymously(function(error, authData) {
    if (error) {
      console.log('Login Failed!', error);
    } else {
      data.sender = authData.uid;
      onAuthSuccess();
    }
  }, {remember: 'sessionOnly'});  // Users will get a new id for every session.
}

下面的程式碼區塊列出地圖上的點擊,這會新增「子項」到您的 Firebase 資料庫。發生此情況時,snapshot.val() 函式會取得項目的資料值,並建立新的 LatLng 物件。

// Listener for when a click is added - add it to the heatmap.
clicks.orderByChild('timestamp').startAt(startTime).on('child_added',
  function(snapshot) {
    var newPosition = snapshot.val();
    var point = new google.maps.LatLng(newPosition.lat, newPosition.lng);
    heatmap.getData().push(point);
  }
);

下面的程式碼會設定 Firebase 以持續記錄時間戳記,並即時刪除地圖上存留時間超過 10 秒的點擊。

/**
 * Set up a Firebase with deletion on clicks older than expirySeconds
 * @param {!google.maps.visualization.HeatmapLayer} heatmap The heatmap to
 * which points are added from Firebase.
 */
function initFirebase(heatmap) {

  // 10 minutes before current time.
  var startTime = new Date().getTime() - (60 * 10 * 1000);

  // Reference to the clicks in Firebase.
  var clicks = firebase.child('clicks');

  // Remove old clicks.
  clicks.orderByChild('timestamp').endAt(startTime).on('child_added',
    function(snapshot) {
      snapshot.ref().remove();
    }
  );

}

/**
 * Adds a click to firebase.
 * @param {Object} data The data to be added to firebase.
 *     It contains the lat, lng, sender and timestamp.
 */
function addToFirebase(data) {
  getTimestamp(function(timestamp) {
    // Add the new timestamp to the record data.
    data.timestamp = timestamp;
    var ref = firebase.child('clicks').push(data, function(err) {
      if (err) {  // Data was not written to firebase.
        console.log(err);
      }
    });
  });
}

/**
 * Also called each time the map is clicked.
 * Updates the last_message/ path with the current timestamp.
 * @param {function(Date)} addClick After the last message timestamp has been updated,
 *     this function is called with the current timestamp to add the
 *     click to the firebase.
 */
function getTimestamp(addClick) {
  // Reference to location for saving the last click time.
  var ref = firebase.child('last_message/' + data.sender);

  ref.onDisconnect().remove();  // Delete reference from firebase on disconnect.

  // Set value to timestamp.
  ref.set(Firebase.ServerValue.TIMESTAMP, function(err) {
    if (err) {  // Write to last message was unsuccessful.
      console.log(err);
    } else {  // Write to last message was successful.
      ref.once('value', function(snap) {
        addClick(snap.val());  // Add click with same timestamp.
      }, function(err) {
        console.log(err);
      });
    }
  });
}

將此節中的所有 JavaScript 程式碼複製到您的 firebasemap.js 檔案中。

建立熱度圖

下一個步驟是建立熱度圖,以圖形化方式為檢視者顯示地圖上不同位置的相對點擊數目。若要深入了解,請閱讀熱度圖指南

initMap() 函式內新增下面的程式碼以建立熱度圖。

// Create a heatmap.
var heatmap = new google.maps.visualization.HeatmapLayer({
  data: [],
  map: map,
  radius: 16
});

下面的程式碼會觸發 initFirebaseaddToFirebasegetTimestamp 函式。

initAuthentication(initFirebase.bind(undefined, heatmap));

接著,將標記程式碼取代為下面的行,以將 Firebase 資料庫中的任何項目顯示為此熱度圖上的點。

heatmap.getData().push(point);

立即嘗試

在您的網頁瀏覽器中重新載入 index.html 檔案。若使用者從其他瀏覽器按一下此地圖,您應該會看到點出現在地圖上。

在熱度圖上建立點

請注意,若您按一下熱度圖,它還不會建立點。

下面的程式碼會在 initMap() 內(位於建立地圖的程式碼後方)新增接聽程式。此程式碼會接聽來自每個點擊的資料、將您的點擊位置儲存在 Firebase 資料庫中,並在您的熱度圖上顯示該點。

// Listen for clicks and add the location of the click to firebase.
map.addListener('click', function(e) {
  data.lat = e.latLng.lat();
  data.lng = e.latLng.lng();
  addToFirebase(data);
});

立即嘗試

按一下地圖上的位置以在您的熱度圖上建立點。

您現在已經擁有使用 Firebase 與 Google Maps JavaScript API 且可完整運作的即時應用程式。

當您按一下熱度圖時,點擊的緯度與經度現在應該會出現在您的 Firebase 資料庫中。您可以透過登入您的 Firebase 帳戶並瀏覽您應用程式的資料索引標籤以查看此內容。此時,若其他人在您的地圖上按一下,您與該人都可以在地圖上看到那些點。即使使用者關閉該頁面,點擊位置也會跨工作階段保留。若要測試即時協力作業功能,請在兩個不同的視窗中開啟該頁面。標記應該會即時出現在兩個視窗中。

深入瞭解

Firebase 是一個會將資料儲存為 JSON 並即時同步到所有已連線用戶端的應用程式平台。即使您的應用程式離線,也可以使用。此教學課程使用其即時資料庫。

傳送您對下列選項的寶貴意見...

這個網頁
Google Maps JavaScript API
Google Maps JavaScript API
需要協助嗎?請前往我們的支援網頁