0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 1 year has passed since last update.

TypeScript + Reactを基本からまとめてみた【3】【Setting up Google Maps】

Last updated at Posted at 2022-04-11

環境の準備

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&region=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キーの取得方法【注意点も解説】

0
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?