在 React 應用程式中加入 Google 地圖

1. 事前準備

在本程式碼研究室中,您將學習如何開始使用 Google Maps JavaScript API 的 vis.gl/react-google-map 程式庫,在 React 應用程式中加入 Google 地圖。您將瞭解如何完成設定、載入 Maps JavaScript API、顯示第一張地圖、使用標記和標記叢集、在地圖上繪製內容,以及處理使用者互動。

必要條件

  • 具備 JavaScript、HTML 和 CSS 的基礎知識

課程內容

  • 如何開始使用 Google 地圖平台的 vis.gl/react-google-map 程式庫。
  • 如何以宣告方式載入 Maps JavaScript API。
  • 如何在 React 應用程式中載入地圖。
  • 如何使用標記、自訂標記和標記叢集。
  • 如何使用 Maps JavaScript API 事件系統提供使用者互動功能。
  • 如何動態控管地圖。
  • 如何在 Google 地圖上繪圖。

需求條件

  • 已啟用計費功能的 Google Cloud 帳戶。
  • 已啟用 Maps JavaScript API 的 Google 地圖平台 API 金鑰。
  • 電腦上已安裝 Node.js
  • 您選擇的文字編輯器或 IDE。
  • Google Maps JavaScript API 的 vis.gl/react-google-map 程式庫。
  • googlemaps/markerclusterer 程式庫

設定 Google 地圖平台

如果您尚未建立 Google Cloud Platform 帳戶,以及啟用計費功能的專案,請參閱「開始使用 Google 地圖平台」指南,建立帳單帳戶和專案。

  1. Cloud 控制台中,按一下專案下拉式選單,然後選取要用於本程式碼研究室的專案。

  1. Google Cloud Marketplace 中,啟用本程式碼研究室所需的 Google 地圖平台 API 和 SDK。如要瞭解如何操作,請觀看這部影片或參閱這份說明文件
  2. 在 Cloud Console 的「憑證」頁面中產生 API 金鑰。你可以按照這部影片這份文件中的步驟操作。所有 Google 地圖平台要求都需要 API 金鑰。

2. 做好準備

下載範例專案

如要下載範例專案範本和解決方案程式碼,請按照下列步驟操作:

  1. 下載或分叉 GitHub 存放區。入門專案位於 /starter 目錄,內含完成程式碼研究室所需的基礎檔案結構。您會在 /starter/src 目錄中完成所有工作。
git clone https://github.com/googlemaps-samples/codelab-maps-platform-101-react-js.git

或者,您也可以點選這個按鈕下載原始碼。

  1. 前往 /starter 目錄並安裝 npm。這會安裝 package.json 檔案中列出的所有必要依附元件。
cd starter && npm install
  1. 仍在 /starter 目錄中時:
npm start

我們已為您設定範例專案,方便您使用 Vite 開發伺服器,編譯及執行您在本機編寫的程式碼。每當您變更程式碼,Vite 開發伺服器也會自動在瀏覽器中重新載入應用程式。如果您按照建構程序結尾提供的連結操作,應該會看到顯示「Hello, world!」的網頁。

  1. 如要執行完整解決方案程式碼,請前往 /solution 目錄,並完成相同的設定步驟。

3. 載入 Maps JavaScript API

使用 Google 地圖平台網頁服務的基礎是 Maps JavaScript API。這個 API 提供 JavaScript 介面,可使用 Google 地圖平台的所有功能,包括地圖、標記、繪圖工具和其他 Google 地圖平台服務 (例如 Places)。

如要使用 React 架構載入 Maps JavaScript API,您需要使用 vis.gl/react-google-map 程式庫中的 APIProvider 元件。這個元件可新增至應用程式的任何層級,通常位於頂端,且會算繪所有未修改的子元件。除了處理 Maps JavaScript API 的載入作業,這個程式庫也會為其他元件和掛鉤提供背景資訊和函式。APIProvider 包含在 vis.gl/react-google-map 程式庫中,因此您先前執行 npm install 時已安裝這個項目。

如要使用 APIProvider 元件,請按照下列步驟操作:

  1. 開啟 /src/app.tsx 檔案,您會在該檔案中完成本程式碼研究室的所有工作。
  2. 在檔案頂端,從 @vis.gl/react-google-maps 程式庫匯入 APIProvider 類別:
import {APIProvider} from '@vis.gl/react-google-maps';
  1. App 函式定義中,使用上一個步驟建立的 API 金鑰設定 APIProvider 元件的 apiKey 參數,並使用控制台記錄訊息設定 onLoad 屬性:
<APIProvider apiKey={'Your API key here'} onLoad={() => console.log('Maps API has loaded.')}>

APIProvider 元件會採用一系列屬性,指定載入 Maps JavaScript API 的各種選項,包括 Google 地圖平台 API 金鑰、要載入的 API 版本,以及要載入的任何其他 Maps JavaScript API 程式庫。

Google 地圖 API 金鑰是 APIProvider 運作的唯一必要屬性,我們加入 onLoad 屬性是為了示範。詳情請參閱「<APIProvider> 元件」。

您的 app.tsx 檔案看起來會像這樣:

import React from 'react';
import {createRoot} from "react-dom/client";
import {APIProvider} from '@vis.gl/react-google-maps';

const App = () => (
 <APIProvider apiKey={'Your API key here'} onLoad={() => console.log('Maps API has loaded.')}>
   <h1>Hello, world!</h1>
 </APIProvider>
);

const root = createRoot(document.getElementById('app'));
root.render(<App />);

export default App;

如果一切順利,您應該會在瀏覽器控制台中看到 console.log 陳述式。載入 Maps JavaScript API 後,您就可以在下一個步驟中算繪動態地圖。

4. 顯示地圖

現在要顯示第一張地圖了!

Maps JavaScript API 最常用的部分是 google.maps.Map,這個類別可讓您建立及操控地圖例項。vis.gl/react-google-map 程式庫會將這個類別包裝在 Map 元件中。首先,匯入 MapMapCameraChangedEvent 類別。

import {APIProvider, Map, MapCameraChangedEvent} from '@vis.gl/react-google-maps';

Map 元件支援地圖的各種不同設定。在本程式碼研究室中,您會使用下列設定:

  • defaultCenter,可設定地圖中心的經緯度。
  • defaultZoom,可設定地圖的初始縮放等級。
  • 如要顯示地圖,請在 APIProvider 標記中加入下列程式碼,將地圖中心設為澳洲雪梨,並將縮放等級設為 13,這是顯示市中心的適當縮放等級:
 <Map
      defaultZoom={13}
      defaultCenter={ { lat: -33.860664, lng: 151.208138 } }
      onCameraChanged={ (ev: MapCameraChangedEvent) =>
        console.log('camera changed:', ev.detail.center, 'zoom:', ev.detail.zoom)
      }>
</Map>

現在瀏覽器中應該會顯示雪梨地圖:

761c8c51c6631174.png

回顧一下,在本節中,您使用 <Map> 元件顯示地圖,並透過屬性設定地圖的初始狀態。您也使用事件擷取攝影機變更的時間。

您的 app.tsx 檔案看起來會像這樣:

import React from 'react';
import {createRoot} from "react-dom/client";
import {APIProvider, Map, MapCameraChangedEvent} from '@vis.gl/react-google-maps';

const App = () => (
 <APIProvider apiKey={'Your API key here'} onLoad={() => console.log('Maps API has loaded.')}>
   <Map
      defaultZoom={13}
      defaultCenter={ { lat: -33.860664, lng: 151.208138 } }
      onCameraChanged={ (ev: MapCameraChangedEvent) =>
        console.log('camera changed:', ev.detail.center, 'zoom:', ev.detail.zoom)
      }>
   </Map>
 </APIProvider>
);

const root = createRoot(document.getElementById('app'));
root.render(<App />);

export default App;

5. 新增雲端式地圖樣式設定

如要使用進階標記,則必須提供地圖 ID,您可以使用進階標記在雪梨地圖上標示搜尋點。地圖 ID 也用於雲端式地圖樣式設定。

您可以使用雲端式地圖樣式設定自訂地圖樣式。

建立地圖 ID

如果尚未建立地圖 ID 並與地圖樣式建立關聯,請參閱「地圖 ID」指南,完成下列步驟:

  1. 建立地圖 ID。
  2. 將地圖 ID 與地圖樣式建立關聯。

如要使用您建立的地圖 ID,請設定 <Map> 元件的 mapId 屬性:

<Map
    defaultZoom={13}
    defaultCenter={ { lat: -33.860664, lng: 151.208138 } }
    mapId='DEMO_MAP_ID'
    onCameraChanged={ (ev: MapCameraChangedEvent) =>
        console.log('camera changed:', ev.detail.center, 'zoom:', ev.detail.zoom)
    }>
</Map>

地圖上應該會顯示您選取的樣式!

6. 在地圖中加入標記

開發人員會使用 Maps JavaScript API 執行許多操作,但將標記放在地圖上絕對是最常見的做法。標記可讓您在地圖上顯示特定點,是處理使用者互動的常見 UI 元素。如果您使用過 Google 地圖,可能對預設標記很熟悉,如下所示:

d9a6513b82a2f1e1.png

如要使用 AdvancedMarker 元件在地圖上放置標記,請按照下列步驟操作:

  1. 建立物件清單,代表雪梨地區的搜尋點,並將清單放在匯入內容下方,也就是 App 定義之外:
type Poi ={ key: string, location: google.maps.LatLngLiteral }
const locations: Poi[] = [
  {key: 'operaHouse', location: { lat: -33.8567844, lng: 151.213108  }},
  {key: 'tarongaZoo', location: { lat: -33.8472767, lng: 151.2188164 }},
  {key: 'manlyBeach', location: { lat: -33.8209738, lng: 151.2563253 }},
  {key: 'hyderPark', location: { lat: -33.8690081, lng: 151.2052393 }},
  {key: 'theRocks', location: { lat: -33.8587568, lng: 151.2058246 }},
  {key: 'circularQuay', location: { lat: -33.858761, lng: 151.2055688 }},
  {key: 'harbourBridge', location: { lat: -33.852228, lng: 151.2038374 }},
  {key: 'kingsCross', location: { lat: -33.8737375, lng: 151.222569 }},
  {key: 'botanicGardens', location: { lat: -33.864167, lng: 151.216387 }},
  {key: 'museumOfSydney', location: { lat: -33.8636005, lng: 151.2092542 }},
  {key: 'maritimeMuseum', location: { lat: -33.869395, lng: 151.198648 }},
  {key: 'kingStreetWharf', location: { lat: -33.8665445, lng: 151.1989808 }},
  {key: 'aquarium', location: { lat: -33.869627, lng: 151.202146 }},
  {key: 'darlingHarbour', location: { lat: -33.87488, lng: 151.1987113 }},
  {key: 'barangaroo', location: { lat: - 33.8605523, lng: 151.1972205 }},
];

const App = () => (
  ...
);
  1. 使用 <Pin> 元素自訂圖釘:
<Pin background={'#FBBC04'} glyphColor={'#000'} borderColor={'#000'} />
  1. 建立自訂元件,使用進階標記算繪清單,並將此元件放在 App 的定義下方:
const App = () => (
  ...
);

const PoiMarkers = (props: {pois: Poi[]}) => {
  return (
    <>
      {props.pois.map( (poi: Poi) => (
        <AdvancedMarker
          key={poi.key}
          position={poi.location}>
        <Pin background={'#FBBC04'} glyphColor={'#000'} borderColor={'#000'} />
        </AdvancedMarker>
      ))}
    </>
  );
};
  1. PoiMarkers 元件新增為 Map 元件的子項:
<Map
  ... map properties ...
>
  <PoiMarkers pois={locations} />
</Map>
  1. 最後,將 PinAdvancedMarker 新增至匯入項目。
import {
  APIProvider,
  Map,
  AdvancedMarker,
  MapCameraChangedEvent,
  Pin
} from '@vis.gl/react-google-maps';

地圖上應該會顯示自訂的進階標記:

98d12a994e12a2c1.png

7. 啟用標記叢集功能

如果使用大量標記或彼此距離很近的標記,可能會發生標記重疊或過於擁擠的問題,導致使用者體驗不佳。舉例來說,在最後一個步驟中建立標記後,您可能會發現以下情況:

98d12a994e12a2c1.png

這時標記叢集功能就能派上用場。標記叢集是另一個常見的實作功能,可將鄰近的標記分組為單一圖示,並根據縮放等級變更,如下所示:

3da24a6b737fe499.png

標記叢集演算法會將地圖的可見區域劃分為格線,然後將同一儲存格中的圖示叢集化。好消息是,您不必擔心這些問題,因為 Google 地圖平台團隊建立了一個實用的開放原始碼公用程式庫,名為 MarkerClustererPlus,可自動為您完成所有作業。您可以在 GitHub 上查看 MarkerClustererPlus 程式庫的來源

如要啟用標記叢集功能,請按照下列步驟操作:

  1. app.tsx 檔案頂端,更新並新增至程式庫匯入和支援的型別。
import React, {useEffect, useState, useRef, useCallback} from 'react';
import {createRoot} from "react-dom/client";
import {
    APIProvider,
    Map,
    AdvancedMarker,
    MapCameraChangedEvent,
    useMap,
    Pin
  } from '@vis.gl/react-google-maps';
  import {MarkerClusterer} from '@googlemaps/markerclusterer';
  import type {Marker} from '@googlemaps/markerclusterer';

在本程式碼研究室的範本專案中,MarkerClustererPlus 公用程式庫已包含在 package.json 檔案中宣告的依附元件中,因此您在本程式碼研究室一開始執行 npm install 時,就已安裝該程式庫。

  1. PoiMarkers 元件中,為 MarkerClusterer 和支援元素建立變數。

您需要地圖的執行個體來初始化 MarkerClusterer。從 useMap() 勾點取得該執行個體:

const map = useMap();
  1. 建立儲存在狀態變數中的標記清單:
const [markers, setMarkers] = useState<{[key: string]: Marker}>({});
  1. 將叢集器儲存為參照:
const clusterer = useRef<MarkerClusterer | null>(null);
  1. 同樣在 PoiMarkers 元件中,建立 MarkerClusterer 的例項,並將標記叢集要顯示的 Map 例項傳遞給該例項:
 useEffect(() => {
    if (!map) return;
    if (!clusterer.current) {
      clusterer.current = new MarkerClusterer({map});
    }
  }, [map]);
  1. 建立效果,在標記清單變更時更新叢集:
useEffect(() => {
    clusterer.current?.clearMarkers();
    clusterer.current?.addMarkers(Object.values(markers));
  }, [markers]);
  1. 建立函式,為新標記鑄造參照:
const setMarkerRef = (marker: Marker | null, key: string) => {
    if (marker && markers[key]) return;
    if (!marker && !markers[key]) return;

    setMarkers(prev => {
      if (marker) {
        return {...prev, [key]: marker};
      } else {
        const newMarkers = {...prev};
        delete newMarkers[key];
        return newMarkers;
      }
    });
  };
  1. AdvancedMarker 元素中使用這個方法,為每個標記建立參照。
<AdvancedMarker
  key={poi.key}
  position={poi.location}
  ref={marker => setMarkerRef(marker, poi.key)}
  >
    <Pin background={'#FBBC04'} glyphColor={'#000'} borderColor={'#000'} />
</AdvancedMarker>

地圖上現在應該會顯示標記叢集:

3da24a6b737fe499.png

縮放畫面時,MarkerClustererPlus 會自動重新編號及調整叢集大小。您也可以點按任一標記叢集圖示,放大檢視該叢集中的所有標記。

d5e75480e9abd3c7.png

回顧一下,在本節中,您匯入了開放原始碼 MarkerClustererPlus 公用程式庫,並使用該程式庫建立 MarkerClusterer 的執行個體。在 React 狀態和參照的協助下,該執行個體會自動將您在上一步驟中建立的標記叢集化。

您的 PoiMarkers 元件應如下所示:

const PoiMarkers = (props: { pois: Poi[] }) => {
  const map = useMap();
  const [markers, setMarkers] = useState<{[key: string]: Marker}>({});
  const clusterer = useRef<MarkerClusterer | null>(null);

  // Initialize MarkerClusterer, if the map has changed
  useEffect(() => {
    if (!map) return;
    if (!clusterer.current) {
      clusterer.current = new MarkerClusterer({map});
    }
  }, [map]);

  // Update markers, if the markers array has changed
  useEffect(() => {
    clusterer.current?.clearMarkers();
    clusterer.current?.addMarkers(Object.values(markers));
  }, [markers]);

  const setMarkerRef = (marker: Marker | null, key: string) => {
    if (marker && markers[key]) return;
    if (!marker && !markers[key]) return;

    setMarkers(prev => {
      if (marker) {
        return {...prev, [key]: marker};
      } else {
        const newMarkers = {...prev};
        delete newMarkers[key];
        return newMarkers;
      }
    });
  };

  return (
    <>
      {props.pois.map( (poi: Poi) => (
        <AdvancedMarker
          key={poi.key}
          position={poi.location}
          ref={marker => setMarkerRef(marker, poi.key)}
          >
            <Pin background={'#FBBC04'} glyphColor={'#000'} borderColor={'#000'} />
        </AdvancedMarker>
      ))}
    </>
  );
};

接下來,我們來瞭解如何處理使用者互動。

8. 新增使用者互動

現在您已製作出精美的地圖,顯示雪梨最熱門的觀光景點。在本節中,您將透過 Maps JavaScript API 的事件系統,新增一些使用者互動的處理方式,進一步提升地圖的使用者體驗。

Maps JavaScript API 提供完善的事件系統,可使用 JavaScript 事件處理常式,讓您在程式碼中處理各種使用者互動。舉例來說,您可以建立事件監聽器,在使用者點選地圖和標記、平移地圖檢視畫面、放大及縮小時,觸發程式碼執行。

如要為標記新增 click 監聽器,然後以程式輔助方式平移地圖,使點選的標記顯示在地圖中央,請按照下列步驟操作:

  1. 建立 click 處理常式回呼。

PoiMarkers 元件中,使用 React 的 useCallback() 定義 click 處理常式。

每當使用者點選或輕觸標記時,系統就會觸發 click 事件,並以 JSON 物件的形式傳回事件,其中包含所點選 UI 元素的相關資訊。如要提升地圖的使用者體驗,您可以處理 click 事件,並使用其 LatLng 物件取得所點選標記的經緯度。

取得經緯度後,請將其傳遞至 Map 執行個體的內建 panTo() 函式,方法是在事件處理常式的回呼函式中加入下列內容,讓地圖平滑平移,並以點選的標記為中心:

const PoiMarkers = (props: { pois: Poi[] }) => {
...
const handleClick = useCallback((ev: google.maps.MapMouseEvent) => {
    if(!map) return;
    if(!ev.latLng) return;
    console.log('marker clicked:', ev.latLng.toString());
    map.panTo(ev.latLng);
  });
...
};
  1. click 處理常式指派給標記。

vis.gl/react-google-map 程式庫的 AdvancedMarker 元素會公開兩個有助於處理點擊的屬性:

  • clickable:如果為 true,AdvancedMarker 就可點選並觸發 gmp-click 事件,且可供無障礙功能互動。例如,可使用方向鍵進行鍵盤導覽。
  • onClick:發生 click 事件時要呼叫的回呼函式。
  1. 更新 PoiMarkers 算繪,為每個標記指派 click 處理常式:
return (
    <>
      {props.pois.map( (poi: Poi) => (
        <AdvancedMarker
          ... other properties ...
          clickable={true}
          onClick={handleClick}
          >
           ...
        </AdvancedMarker>
      ))}
    </>
  );
  1. 前往瀏覽器並按一下標記。點選標記時,地圖應該會自動平移並重新置中。

回顧一下,在本節中,您使用了 React 的事件系統,將 click 處理常式指派給地圖上的所有標記,從觸發的 click 事件中擷取標記的緯度和經度,並在點選標記時使用這些資訊重新置中地圖。

只差最後一步了!接著,您將使用 Maps JavaScript API 的繪圖功能,進一步提升地圖的使用者體驗。

9. 在地圖上繪圖

到目前為止,您已建立雪梨地圖,其中顯示熱門觀光景點的標記,並處理使用者互動。在本程式碼研究室的最後一個步驟中,您將使用 Maps JavaScript API 的繪圖功能,為地圖體驗新增實用功能。

假設這張地圖的使用者想探索雪梨市,如果使用者點選標記,可以顯示標記周圍的半徑,這會是實用的功能。這樣一來,使用者就能瞭解點選的標記附近有哪些其他目的地。

Maps JavaScript API 包含一組函式,可在地圖上繪製正方形、多邊形、線條和圓形等形狀。vis.gl/react-google-map 程式庫可讓您在 React 中使用這些功能。

接著,您會算繪圓形,在點選標記時,顯示標記周圍 800 公尺 (約半英里) 的半徑。

範例存放區包含 circle 元素的自訂元件。您可以在 src/components/circle.tsx 檔案中找到該函式。

如要允許使用者在地圖上繪製圖案,請按照下列步驟操作:

  1. 更新匯入項目,加入提供的 Circle 元件。
import {Circle} from './components/circle'
  1. 建立圓心狀態變數。

PoiMarkers 元件中擷取圓圈中心的狀態。您將初始狀態設為空值,並利用圓形必須有有效的中心位置 (和半徑) 才會算繪的事實。

const PoiMarkers = (props: { pois: Poi[] }) => {
...
  const [circleCenter, setCircleCenter] = useState(null)
...
};
  1. 處理 click 事件時,更新圓心。

使用事件物件中找到的位置呼叫 setCircleCenter

const handleClick = useCallback((ev: google.maps.MapMouseEvent) => {
    ...
    setCircleCenter(ev.latLng);
  });

Maps JavaScript API 中的繪圖函式提供多種選項,可決定繪製物件在地圖上的顯示方式。如要算繪圓形半徑,請設定圓形元素的屬性,例如顏色和筆劃粗細,以及圓形應置中的位置和半徑。

  1. 在算繪中新增圓圈,並將中心繫結至狀態變數。算繪結果應如下所示:
return (
    <>
      <Circle
          radius={800}
          center={circleCenter}
          strokeColor={'#0c4cb3'}
          strokeOpacity={1}
          strokeWeight={3}
          fillColor={'#3b82f6'}
          fillOpacity={0.3}
        />
      {props.pois.map( (poi: Poi) => (
        <AdvancedMarker
          key={poi.key}
          position={poi.location}
          ref={marker => setMarkerRef(marker, poi.key)}
          clickable={true}
          onClick={handleClick}
          >
            <Pin background={'#FBBC04'} glyphColor={'#000'} borderColor={'#000'} />
        </AdvancedMarker>
      ))}
    </>
  );
};

大功告成!前往瀏覽器,然後按一下其中一個標記。您應該會看到周圍呈現圓形半徑:

d243587f4a9ec4a6.png

10. 恭喜

您已使用 Google 地圖平台的 vis.gl/react-google-map 程式庫建構第一個網頁應用程式,包括載入 Maps JavaScript API、載入地圖、使用標記、控制地圖及在地圖上繪製內容,以及新增使用者互動。

如要查看完成的程式碼,請參閱 /solutions 目錄。

瞭解詳情