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 地圖平台」指南,建立帳單帳戶和專案。
- 在 Cloud 控制台中,按一下專案下拉式選單,然後選取要用於本程式碼研究室的專案。
- 在 Google Cloud Marketplace 中,啟用本程式碼研究室所需的 Google 地圖平台 API 和 SDK。如要瞭解如何操作,請觀看這部影片或參閱這份說明文件。
- 在 Cloud Console 的「憑證」頁面中產生 API 金鑰。你可以按照這部影片或這份文件中的步驟操作。所有 Google 地圖平台要求都需要 API 金鑰。
2. 做好準備
下載範例專案
如要下載範例專案範本和解決方案程式碼,請按照下列步驟操作:
- 下載或分叉 GitHub 存放區。入門專案位於
/starter
目錄,內含完成程式碼研究室所需的基礎檔案結構。您會在/starter/src
目錄中完成所有工作。
git clone https://github.com/googlemaps-samples/codelab-maps-platform-101-react-js.git
或者,您也可以點選這個按鈕下載原始碼。
- 前往
/starter
目錄並安裝 npm。這會安裝package.json
檔案中列出的所有必要依附元件。
cd starter && npm install
- 仍在
/starter
目錄中時:
npm start
我們已為您設定範例專案,方便您使用 Vite 開發伺服器,編譯及執行您在本機編寫的程式碼。每當您變更程式碼,Vite 開發伺服器也會自動在瀏覽器中重新載入應用程式。如果您按照建構程序結尾提供的連結操作,應該會看到顯示「Hello, world!」的網頁。
- 如要執行完整解決方案程式碼,請前往
/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
元件,請按照下列步驟操作:
- 開啟
/src/app.tsx
檔案,您會在該檔案中完成本程式碼研究室的所有工作。 - 在檔案頂端,從
@
vis.gl/react-google-maps
程式庫匯入APIProvider
類別:
import {APIProvider} from '@vis.gl/react-google-maps';
- 在
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
元件中。首先,匯入 Map
和 MapCameraChangedEvent
類別。
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>
現在瀏覽器中應該會顯示雪梨地圖:
回顧一下,在本節中,您使用 <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」指南,完成下列步驟:
- 建立地圖 ID。
- 將地圖 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 地圖,可能對預設標記很熟悉,如下所示:
如要使用 AdvancedMarker
元件在地圖上放置標記,請按照下列步驟操作:
- 建立物件清單,代表雪梨地區的搜尋點,並將清單放在匯入內容下方,也就是
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 = () => (
...
);
- 使用
<Pin>
元素自訂圖釘:
<Pin background={'#FBBC04'} glyphColor={'#000'} borderColor={'#000'} />
- 建立自訂元件,使用進階標記算繪清單,並將此元件放在
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>
))}
</>
);
};
- 將
PoiMarkers
元件新增為Map
元件的子項:
<Map
... map properties ...
>
<PoiMarkers pois={locations} />
</Map>
- 最後,將
Pin
和AdvancedMarker
新增至匯入項目。
import {
APIProvider,
Map,
AdvancedMarker,
MapCameraChangedEvent,
Pin
} from '@vis.gl/react-google-maps';
地圖上應該會顯示自訂的進階標記:
7. 啟用標記叢集功能
如果使用大量標記或彼此距離很近的標記,可能會發生標記重疊或過於擁擠的問題,導致使用者體驗不佳。舉例來說,在最後一個步驟中建立標記後,您可能會發現以下情況:
這時標記叢集功能就能派上用場。標記叢集是另一個常見的實作功能,可將鄰近的標記分組為單一圖示,並根據縮放等級變更,如下所示:
標記叢集演算法會將地圖的可見區域劃分為格線,然後將同一儲存格中的圖示叢集化。好消息是,您不必擔心這些問題,因為 Google 地圖平台團隊建立了一個實用的開放原始碼公用程式庫,名為 MarkerClustererPlus
,可自動為您完成所有作業。您可以在 GitHub 上查看 MarkerClustererPlus
程式庫的來源。
如要啟用標記叢集功能,請按照下列步驟操作:
- 在
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
時,就已安裝該程式庫。
- 在
PoiMarkers
元件中,為MarkerClusterer
和支援元素建立變數。
您需要地圖的執行個體來初始化 MarkerClusterer
。從 useMap()
勾點取得該執行個體:
const map = useMap();
- 建立儲存在狀態變數中的標記清單:
const [markers, setMarkers] = useState<{[key: string]: Marker}>({});
- 將叢集器儲存為參照:
const clusterer = useRef<MarkerClusterer | null>(null);
- 同樣在
PoiMarkers
元件中,建立MarkerClusterer
的例項,並將標記叢集要顯示的Map
例項傳遞給該例項:
useEffect(() => {
if (!map) return;
if (!clusterer.current) {
clusterer.current = new MarkerClusterer({map});
}
}, [map]);
- 建立效果,在標記清單變更時更新叢集:
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;
}
});
};
- 在
AdvancedMarker
元素中使用這個方法,為每個標記建立參照。
<AdvancedMarker
key={poi.key}
position={poi.location}
ref={marker => setMarkerRef(marker, poi.key)}
>
<Pin background={'#FBBC04'} glyphColor={'#000'} borderColor={'#000'} />
</AdvancedMarker>
地圖上現在應該會顯示標記叢集:
縮放畫面時,MarkerClustererPlus
會自動重新編號及調整叢集大小。您也可以點按任一標記叢集圖示,放大檢視該叢集中的所有標記。
回顧一下,在本節中,您匯入了開放原始碼 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
監聽器,然後以程式輔助方式平移地圖,使點選的標記顯示在地圖中央,請按照下列步驟操作:
- 建立
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);
});
...
};
- 將
click
處理常式指派給標記。
vis.gl/react-google-map
程式庫的 AdvancedMarker
元素會公開兩個有助於處理點擊的屬性:
clickable
:如果為 true,AdvancedMarker
就可點選並觸發gmp-click
事件,且可供無障礙功能互動。例如,可使用方向鍵進行鍵盤導覽。onClick
:發生click
事件時要呼叫的回呼函式。
- 更新
PoiMarkers
算繪,為每個標記指派click
處理常式:
return (
<>
{props.pois.map( (poi: Poi) => (
<AdvancedMarker
... other properties ...
clickable={true}
onClick={handleClick}
>
...
</AdvancedMarker>
))}
</>
);
- 前往瀏覽器並按一下標記。點選標記時,地圖應該會自動平移並重新置中。
回顧一下,在本節中,您使用了 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
檔案中找到該函式。
如要允許使用者在地圖上繪製圖案,請按照下列步驟操作:
- 更新匯入項目,加入提供的 Circle 元件。
import {Circle} from './components/circle'
- 建立圓心狀態變數。
在 PoiMarkers
元件中擷取圓圈中心的狀態。您將初始狀態設為空值,並利用圓形必須有有效的中心位置 (和半徑) 才會算繪的事實。
const PoiMarkers = (props: { pois: Poi[] }) => {
...
const [circleCenter, setCircleCenter] = useState(null)
...
};
- 處理
click
事件時,更新圓心。
使用事件物件中找到的位置呼叫 setCircleCenter
:
const handleClick = useCallback((ev: google.maps.MapMouseEvent) => {
...
setCircleCenter(ev.latLng);
});
Maps JavaScript API 中的繪圖函式提供多種選項,可決定繪製物件在地圖上的顯示方式。如要算繪圓形半徑,請設定圓形元素的屬性,例如顏色和筆劃粗細,以及圓形應置中的位置和半徑。
- 在算繪中新增圓圈,並將中心繫結至狀態變數。算繪結果應如下所示:
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>
))}
</>
);
};
大功告成!前往瀏覽器,然後按一下其中一個標記。您應該會看到周圍呈現圓形半徑:
10. 恭喜
您已使用 Google 地圖平台的 vis.gl/react-google-map
程式庫建構第一個網頁應用程式,包括載入 Maps JavaScript API、載入地圖、使用標記、控制地圖及在地圖上繪製內容,以及新增使用者互動。
如要查看完成的程式碼,請參閱 /solutions
目錄。