📚
Next.js + TypeScript + Google Map JavaScript APIから機能追加してみる
🎯目的
- 前回はGoogle Mapを表示してみました。今回は機能を追加してみましょう。
- プロトタイプ開発間隔で機能を段階的に追加してみましょう。
今回追加する機能は以下となります。- 緯度経度の表示
- 周辺店舗のリスト表示
- 2点間のマーカー表示
- 2点間の距離表示
前回の内容
前回の記事では、Next.jsとTypeScriptを使用してGoogle Mapを表示する基本的な方法を紹介しました。
今回は、その基礎の上に新しい機能を追加していきます。
緯度、経度を取得してみよう
地図上をクリックしたら、緯度経度が取得できる機能を追加します。
以下コードを修正します。
// src/components/GoogleMap.tsx
// (import分は変更なし)
const GoogleMap: React.FC = () => {
const mapRef = useRef<HTMLDivElement>(null);
const [map, setMap] = useState<google.maps.Map | null>(null);
const [location, setLocation] = useState<{ lat: number; lng: number } | null>(null); // 緯度経度stateの追加
// (省略)
// 以下処理を追加
useEffect(() => {
if (!map) return;
// クリックリスナー追加
map.addListener('click', (event) => {
// 緯度経度の取得
const latitude = event.latLng.lat();
const longitude = event.latLng.lng();
setLocation({ lat: latitude, lng: longitude });
});
}, [map]);
// (省略)
return (
<div>
{/** 地図表示 */}
<div ref={mapRef} style={{ width: INITIALIZE_MAP_WIDTH, height: INITIALIZE_MAP_HEIGHT }} />
{/** 緯度経度表示(追加) */}
{location && (
<div className="mx-5 my-5">
<h2 className="underline text-lg mb-3">Location</h2>
<p>Latitude: {location.lat}</p>
<p>Longitude: {location.lng}</p>
</div>
)}
</div>
);
};
export default GoogleMap;
ここでは、地図上でのクリックイベントを利用して、クリックされた場所の緯度と経度を取得し、画面に表示しています。
スタイルの調整も合わせて行います。
(classNameの修正)
// src/pages/index.tsx
const Home: React.FC = () => {
return (
<section>
<h1 className="underline text-xl mt-3 mb-5 ml-3">Google Maps with Next.js and TypeScript</h1>
<GoogleMap />
</section>
);
};
無事に緯度、経度表示に成功!
周辺店舗を取得してみよう
// src/components/GoogleMap.tsx
// (import分は変更なし)
const GoogleMap: React.FC = () => {
const mapRef = useRef<HTMLDivElement>(null);
const [map, setMap] = useState<google.maps.Map | null>(null);
const [location, setLocation] = useState<{ lat: number; lng: number } | null>(null);
const [shops, setShops] = useState<google.maps.places.PlaceResult[]>([]); // 周辺店舗stateの追加
// (省略)
useEffect(() => {
if (!map) return;
map.addListener('click', (event) => {
// 緯度経度の取得
const latitude = event.latLng.lat();
const longitude = event.latLng.lng();
setLocation({ lat: latitude, lng: longitude });
// 店舗データの取得(追加)
const service = new google.maps.places.PlacesService(map);
service.nearbySearch({
location: { lat: latitude, lng: longitude },
radius: 1000, // 検索範囲(メートル)
type: 'store' // 店舗を検索
}, (results, status) => {
if (status === window.google.maps.places.PlacesServiceStatus.OK) {
setShops(results);
}
});
});
}, [map]);
// (省略)
return (
<div>
{/** 地図表示 */}
<div ref={mapRef} style={{ width: INITIALIZE_MAP_WIDTH, height: INITIALIZE_MAP_HEIGHT }} />
{/** 緯度経度表示 */}
{location && (
<div className="mx-5 my-5">
<h2 className="underline text-lg mb-3">Location</h2>
<p>Latitude: {location.lat}</p>
<p>Longitude: {location.lng}</p>
</div>
)}
{/** 店舗リストの表示(追加) */}
{shops.length > 0 && (
<div className="mx-5 mb-5">
<h2 className="underline text-lg mb-3">Nearby Stores</h2>
<ul className="list-disc list-inside">
{shops.map((shop, index) => (
<li key={index}>{shop.name}</li>
))}
</ul>
</div>
)}
</div>
);
};
export default GoogleMap;
周辺店舗の名称の取得に成功!
2点間のマーカーで結んでみよう
// src/components/GoogleMap.tsx
import { mapPrefectures } from '@/datas/mapPrefectures'; // 都道府県のデータを含むモジュール(追加)
const GoogleMap: React.FC = () => {
const mapRef = useRef<HTMLDivElement>(null);
const [map, setMap] = useState<google.maps.Map | null>(null);
const [location, setLocation] = useState<{ lat: number; lng: number } | null>(null);
const [shops, setShops] = useState<google.maps.places.PlaceResult[]>([]);
const [startLocation, setStartLocation] = useState<string | null>(null); // スタート地点state(追加)
const [endLocation, setEndLocation] = useState<string | null>(null); // ゴール地点state追加
const [directionsRenderer, setDirectionsRenderer] = useState<google.maps.DirectionsRenderer | null>(null); // 道順をマップ上に描画するためのオブジェクト(追加)
// Google Maps APIを使用してルートの方向をレンダリングするための関数(追加)
const renderDirections = (result: google.maps.DirectionsResult) => {
if (directionsRenderer) {
directionsRenderer.setMap(null);
}
// Google Maps APIにおいて、道順をマップ上に描画するためのオブジェクトを取得する
const renderer = new google.maps.DirectionsRenderer();
// 現在のマップオブジェクトを追加する
renderer.setMap(map);
// ルートの結果を設定
renderer.setDirections(result);
setDirectionsRenderer(renderer);
};
useEffect(() => {
if (!map) return;
// スタート地点、ゴール地点の地点間のルートを計算し、その結果を処理する(追加)
if (startLocation && endLocation) {
// 指定された開始地点と終了地点間のルートを計算する為のオブジェクトを取得する
const directionsService = new google.maps.DirectionsService();
// ルート計算のリクエスト
directionsService.route({
origin: mapPrefectures[startLocation], // スタート地点
destination: mapPrefectures[endLocation], // ゴール地点
travelMode: google.maps.TravelMode.DRIVING, // 移動手段(車)
}, (result, status) => {
if (status === google.maps.DirectionsStatus.OK) {
renderDirections(result);
} else {
console.error('Directions request failed due to ' + status);
}
});
}
map.addListener('click', (event) => {
// (省略)
});
}, [map, startLocation, endLocation]);
// (省略)
return (
<div>
<div className="flex">
{/** スタート地点(追加) */}
<div className="ml-3 bg-green-200">
<label>Start Location:</label>
<select value={startLocation || ''} onChange={(e) => setStartLocation(e.target.value)}>
<option value="">--Select--</option>
{/** ここに都道府県リストを表示する */}
{Object.keys(mapPrefectures).map(key => (
<option key={key} value={key}>{key}</option>
))}
</select>
</div>
{/** ゴール地点(追加) */}
<div className="ml-3 bg-green-200">
<label>End Location:</label>
<select value={endLocation || ''} onChange={(e) => setEndLocation(e.target.value)}>
<option value="">--Select--</option>
{/** ここに都道府県リストを表示する */}
{Object.keys(mapPrefectures).map(key => (
<option key={key} value={key}>{key}</option>
))}
</select>
</div>
</div>
{/** 地図表示 */}
<div ref={mapRef} style={{ width: INITIALIZE_MAP_WIDTH, height: INITIALIZE_MAP_HEIGHT }} />
{/** 緯度経度表示 */}
{location && (
<div className="mx-5 my-5">
<h2 className="underline text-lg mb-3">Location</h2>
<p>Latitude: {location.lat}</p>
<p>Longitude: {location.lng}</p>
</div>
)}
{/** 店舗リストの表示 */}
{shops.length > 0 && (
<div className="mx-5 mb-5">
<h2 className="underline text-lg mb-3">Nearby Stores</h2>
<ul className="list-disc list-inside">
{shops.map((shop, index) => (
<li key={index}>{shop.name}</li>
))}
</ul>
</div>
)}
</div>
);
};
export default GoogleMap;
まずはリロードしてセレクトボックスが表示されてるか確認します。
無事表示成功!
次にスタート地点、ゴール地点を選択します。
今回は「北海道」、「東京」の順で選択します。
無事2点間のマーカー表示成功!
2点間の距離を表示してみよう
// src/components/GoogleMap.tsx
// (import分は変更なし)
const GoogleMap: React.FC = () => {
const mapRef = useRef<HTMLDivElement>(null);
const [map, setMap] = useState<google.maps.Map | null>(null);
const [location, setLocation] = useState<{ lat: number; lng: number } | null>(null);
const [shops, setShops] = useState<google.maps.places.PlaceResult[]>([]);
const [startLocation, setStartLocation] = useState<string | null>(null);
const [endLocation, setEndLocation] = useState<string | null>(null);
const [directionsRenderer, setDirectionsRenderer] = useState<google.maps.DirectionsRenderer | null>(null);
const [distance, setDistance] = useState<string | null>(null); // 距離state(追加)
// (省略)
useEffect(() => {
if (!map) return;
if (startLocation && endLocation) {
const directionsService = new google.maps.DirectionsService();
directionsService.route({
origin: mapPrefectures[startLocation],
destination: mapPrefectures[endLocation],
travelMode: google.maps.TravelMode.DRIVING,
}, (result, status) => {
if (status === google.maps.DirectionsStatus.OK) {
renderDirections(result);
// 2点間の距離を取得(追加)
const routeDistance = result.routes[0].legs[0].distance.text;
setDistance(routeDistance);
} else {
console.error('Directions request failed due to ' + status);
}
});
}
map.addListener('click', (event) => {
// (省略)
});
}, [map, startLocation, endLocation]);
// (省略)
return (
<div>
<div className="flex">
{/** スタート地点 */}
<div className="ml-3 bg-green-200">
<label>Start Location:</label>
<select value={startLocation || ''} onChange={(e) => setStartLocation(e.target.value)}>
<option value="">--Select--</option>
{/** ここに都道府県リストを表示する */}
{Object.keys(mapPrefectures).map(key => (
<option key={key} value={key}>{key}</option>
))}
</select>
</div>
{/** ゴール地点 */}
<div className="ml-3 bg-green-200">
<label>End Location:</label>
<select value={endLocation || ''} onChange={(e) => setEndLocation(e.target.value)}>
<option value="">--Select--</option>
{/** ここに都道府県リストを表示する */}
{Object.keys(mapPrefectures).map(key => (
<option key={key} value={key}>{key}</option>
))}
</select>
</div>
{/** 2点間の距離(追加) */}
{distance && (
<div className="flex ml-3">
<h2 className="mr-3 bg-green-200">Route Distance:</h2>
<p>{distance}</p>
</div>
)}
</div>
{/** 地図表示 */}
<div ref={mapRef} style={{ width: INITIALIZE_MAP_WIDTH, height: INITIALIZE_MAP_HEIGHT }} />
{/** 省略 */}
</div>
);
};
export default GoogleMap;
2点間の距離を表示。
無事に表示成功!
終わりに
この記事では、Next.jsとTypeScriptを使用してGoogle Mapに様々な機能を追加しました。
緯度経度の取得、周辺店舗のリスト表示、2点間のマーカー表示、距離表示など、段階的に機能を追加することで、地図アプリケーションの機能を豊かにする方法を探求しました。
最後までお読みいただきありがとうございました!
Discussion