環境の準備
google api keyを取得する
Google Cloud Platformへ行き、コンソールをクリックする。
プロジェクトを作成する。
Create React Appで新しいReactアプリを作成する
npx create-react-app <プロジェクト名> --template typescript
使用しないファイルを削除
App.test.tesx
logo.svg
servicsWorker.ts
reportWebVitals.ts
環境構築
$ npm install node-sass
$ npm install --save-dev @types/googlemaps
コンポーネント・ファイル構成
src
├── Map
├── index.ts
├── Map.scss
└── Map.tsx
├── utils
└── GoogleMapsUtils.ts
├── App.css
├── App.tsx
├── index.tsx
└── react-app-env.d.ts
├── .env
src/Map/index.ts
export { default } from "./Map";
src/Map/Map.scss
.map-container {
display: flex;
justify-content: center;
padding: 1rem;
&__map {
height: 60vh;
width: 100%;
}
}
src/Map/Map.tsx
import React, { useEffect, useRef, useState } from 'react';
import './Map.scss';
interface IMap {
mapType: google.maps.MapTypeId;
mapTypeControl?: boolean;
setDistanceInKm: React.Dispatch<React.SetStateAction<number>>;
}
interface IMarker {
address: string;
latitude: number;
longitude: number;
}
type GoogleLatLng = google.maps.LatLng;
type GoogleMap = google.maps.Map;
type GoogleMarker = google.maps.Marker;
type GooglePolyline = google.maps.Polyline;
const Map: React.FC<IMap> = ({
mapType,
mapTypeControl = false,
setDistanceInKm,
}) => {
const ref = useRef<HTMLDivElement>(null);
const [map, setMap] = useState<GoogleMap>();
const [marker, setMarker] = useState<IMarker>();
const [homeMarker, setHomeMarker] = useState<GoogleMarker>();
const [googleMarkers, setGoogleMarkers] = useState<GoogleMarker[]>([]);
const [listenerIdArray, setListenerIdArray] = useState<any[]>([]);
const [LastLineHook, setLastLineHook] = useState<GooglePolyline>();
const startMap = (): void => {
if (!map) {
defaultMapStart();
} else {
const homeLocation = new google.maps.LatLng(65.166013499, 13.3698147);
setHomeMarker(addHomeMarker(homeLocation));
}
};
useEffect(startMap, [map]);
const defaultMapStart = (): void => {
const defaultAddress = new google.maps.LatLng(65.166013499, 13.3698147);
initMap(4, defaultAddress);
};
const initEventListener = (): void => {
if (map) {
google.maps.event.addListener(map, 'click', function (e) {
coordinateToAddress(e.latLng);
});
}
};
useEffect(initEventListener, [map]);
const coordinateToAddress = async (coordinate: GoogleLatLng) => {
const geocoder = new google.maps.Geocoder();
await geocoder.geocode(
{ location: coordinate },
function (results, status) {
if (status === 'OK') {
setMarker({
address: results[0].formatted_address,
latitude: coordinate.lat(),
longitude: coordinate.lng(),
});
}
}
);
};
useEffect(() => {
if (marker) {
addMarker(new google.maps.LatLng(marker.latitude, marker.longitude));
}
}, [marker]);
const addMarker = (location: GoogleLatLng): void => {
const marker: GoogleMarker = new google.maps.Marker({
position: location,
map: map,
icon: getIconAttributes('#000000'),
});
setGoogleMarkers((googleMarkers) => [...googleMarkers, marker]);
const listenerId = marker.addListener('click', () => {
const homePos = homeMarker?.getPosition();
const markerPos = marker.getPosition();
if (homePos && markerPos) {
const distanceInMeters =
google.maps.geometry.spherical.computeDistanceBetween(
homePos,
markerPos
);
setDistanceInKm(Math.round(distanceInMeters / 1000));
if (LastLineHook) {
LastLineHook.setMap(null);
}
const line = new google.maps.Polyline({
path: [
{ lat: homePos.lat(), lng: homePos.lng() },
{ lat: markerPos.lat(), lng: markerPos.lng() },
],
icons: [
{
icon: {
path: google.maps.SymbolPath.FORWARD_OPEN_ARROW,
},
offset: '100%',
},
],
map: map,
});
setLastLineHook(line);
}
});
setListenerIdArray((listenerIdArray) => [...listenerIdArray, listenerId]);
};
useEffect(() => {
listenerIdArray.forEach((listenerId) => {
google.maps.event.removeListener(listenerId);
});
setListenerIdArray([]);
setGoogleMarkers([]);
googleMarkers.forEach((googleMarker) => {
const markerPosition = googleMarker.getPosition();
if (markerPosition) {
addMarker(markerPosition);
}
});
}, [LastLineHook]);
const addHomeMarker = (location: GoogleLatLng): GoogleMarker => {
const homeMarkerConst: GoogleMarker = new google.maps.Marker({
position: location,
map: map,
icon: {
url: window.location.origin + '/assets/images/homeAddressMarker.png',
},
});
homeMarkerConst.addListener('click', () => {
map?.panTo(location);
map?.setZoom(6);
});
return homeMarkerConst;
};
const getIconAttributes = (iconColor: string) => {
return {
path: 'M11.0639 15.3003L26.3642 2.47559e-05L41.6646 15.3003L26.3638 51.3639L11.0639 15.3003 M22,17.5a4.5,4.5 0 1,0 9,0a4.5,4.5 0 1,0 -9,0Z',
fillColor: iconColor,
fillOpacity: 0.8,
strokeColor: 'pink',
strokeWeight: 2,
anchor: new google.maps.Point(30, 50),
};
};
const initMap = (zoomLevel: number, address: GoogleLatLng): void => {
if (ref.current) {
setMap(
new google.maps.Map(ref.current, {
zoom: zoomLevel,
center: address,
mapTypeControl: mapTypeControl,
streetViewControl: false,
rotateControl: false,
scaleControl: true,
fullscreenControl: false,
panControl: false,
zoomControl: true,
gestureHandling: 'cooperative',
mapTypeId: mapType,
draggableCursor: 'pointer',
})
);
}
};
return (
<div className='map-container'>
<div ref={ref} className='map-container__map'></div>
</div>
);
};
export default Map;
src/utils/GoogleMapsUtils.ts
export const loadMapApi = () => {
const mapsURL = `https://maps.googleapis.com/maps/api/js?key=${process.env.REACT_APP_GOOGLE_MAPS_API_KEY}&libraries=geometry,places&language=no®ion=NO&v=quarterly`;
const scripts = document.getElementsByTagName('script');
// 既存のスクリプトタグを調べ、見つかったらgoogle mapのapiタグを返します。
for (let i = 0; i < scripts.length; i++) {
if (scripts[i].src.indexOf(mapsURL) === 0) {
return scripts[i];
}
}
const googleMapScript = document.createElement('script');
googleMapScript.src = mapsURL;
googleMapScript.async = true;
googleMapScript.defer = true;
window.document.body.appendChild(googleMapScript);
return googleMapScript;
};
src/App.css
.distance-info {
margin: 1rem;
padding: 1rem;
font-size: 1.5em;
background: #7fdbff;
border-radius: 2rem;
}
src/App.tsx
import React, { useEffect, useState } from 'react';
import './App.css';
import Map from './Map/Map';
import { loadMapApi } from './utils/GoogleMapsUtils';
function App() {
const [scriptLoaded, setScriptLoaded] = useState(false);
const [distanceInKm, setDistanceInKm] = useState<number>(-1);
useEffect(() => {
const googleMapScript = loadMapApi();
googleMapScript.addEventListener('load', function () {
setScriptLoaded(true);
});
}, []);
const renderDistanceSentence = () => {
return (
<div className='distance-info'>
{`Distance between selected marker and home address is ${distanceInKm}km.`}
</div>
);
};
return (
<div className='App'>
{scriptLoaded && (
<Map
mapType={google.maps.MapTypeId.ROADMAP}
mapTypeControl={true}
setDistanceInKm={setDistanceInKm}
/>
)}
{distanceInKm > -1 && renderDistanceSentence()}
</div>
);
}
export default App;
src/index.css
body {
margin: 0;
}
src/index.tsx
import React from "react";
import ReactDOM from "react-dom";
import "./index.css";
import App from "./App";
ReactDOM.render(
<React.StrictMode>
<App />
</React.StrictMode>,
document.getElementById("root")
);
.env
REACT_APP_GOOGLE_MAPS_API_KEY=
参考サイト
Setting up Google Maps with React + Typescript (Part 1)
Creating Markers on Google Maps with React + Typescript (Part 2)
Google Maps APIを使ってみよう
【WordPress】Google Maps Platform APIキーの取得方法【注意点も解説】