reactにgoogle map機能を実装

# 

npx create-react-app ****でアプリを作成

インストール
```:ターミナル

npm install firebase
npm install "@vis.gl/react-google-maps"
npm install "@googlemaps/react-wrapper"
npm install react-router-dom
```

#firebase.jsを設定
```:firebase.js
// firebase.js
import { initializeApp } from "firebase/app";
import { getFirestore } from "firebase/firestore"; // Firestore をインポート

const firebaseConfig = {
  apiKey: "AIzaSyCyO*******",
  authDomain: "react--map-64573.firebaseapp.com",
  projectId: "react--map-64573",
  storageBucket: "react--map-64573.appspot.com",
  messagingSenderId: "289752151691",
  appId: "1:28******:web:3e8385*************",
  measurementId: "G-V********",
};

// Firebase を初期化
const app = initializeApp(firebaseConfig);
const db = getFirestore(app); // Firestore インスタンスを作成
export { db }; // db をエクスポート
```

# google map APIを取得
https://developers.google.com/maps/documentation/javascript/get-api-key?hl=ja

# .env （拡張子なし）ファイルを作成し、最上部に配置
``` :.env
REACT_APP_GOOGLE_MAPS_API_KEY=あなたのAPIキ
```
# gitignoreにつ追加
```:  

# misc
.DS_Store
.env
.env.local
```
## 場所を指定しfirebaseに保存
```:GoogleMapSelect.js
// GoogleMapSelect

import React from "react";
import { BrowserRouter as Router, Route, Routes } from "react-router-dom";
import { APIProvider } from "@vis.gl/react-google-maps";
import GoogleMapSelect from "./Components/GoogleMapSelect";

const App = () => (
  <APIProvider apiKey={process.env.REACT_APP_GOOGLE_MAPS_API_KEY}>
    <Router>
      <Routes>
 
        {/* 他のルートをここに追加できます */}
        <Route path="/" element={<GoogleMapSelect />} />
      </Routes>
    </Router>
  </APIProvider>
);
export default App;
```

## 選択して位置を保存するコード
```: GoogleMapSelect.js
// GoogleMapSelect.js
import React from "react";
import { Wrapper } from "@googlemaps/react-wrapper";
import { collection, addDoc } from "firebase/firestore";
import { db } from "../firebase"; // Firebaseの設定ファイルをインポート
import "./GoogleMapSelect.css";

/**
 * Google Mapsの読み込み状態を表示
 * @param {string} status - Google Mapsの読込状態。"FAIL"は、読み込み失敗メッセージを表示。
 * @returns {JSX.Element} 読み込み状態に応じたメッセージを含むJSX要素
 */
const render = (status) => {
  if (status === "FAIL") {
    return <h1>Google Mapsの読み込みに失敗しました</h1>;
  }
  return <h1>{status}</h1>;
};

/**
 * GoogleMapSelectコンポーネント
 * @returns
 */
const GoogleMapSelect = () => {
  const [center, setCenter] = React.useState({ lat: 35.0116, lng: 135.7681 }); // 初期位置を京都に設定
  const [zoom, setZoom] = React.useState(15); // ズームレベルの初期値

  /**
   * 地図上の中心位置を更新
   */
  const updateCenter = (map) => {
    if (map) {
      const center = map.getCenter().toJSON();
      setCenter(center);
      setZoom(map.getZoom()); // ズームレベルも更新
    }
  };

  /**
   * Firestoreに位置情報を保存する関数
   */
  const saveLocation = async () => {
    if (zoom < 19) {
      alert("もう少し拡大して位置を指定してください。");
      return;
    }

    try {
      // 現在の地図の中心を位置情報として保存
      const position = {
        lat: center.lat,
        lng: center.lng,
        timestamp: new Date(),
      };

      // Firestoreに位置情報を保存
      const docRef = await addDoc(collection(db, "locations"), position);
      console.log("Location saved to database with ID:", docRef.id);
    } catch (error) {
      console.error("Error saving location to database", error);
    }
  };

  /**
   * 地図表示の際の初期値を設定
   */
  const mapOptions = {
    center: center,
    zoom: zoom, // 動的にズームレベルを設定
    styles: [],
  };

  return (
    <div className="container" style={{ display: "flex", height: "100vh" }}>
      <div className="map-title">地図の世界</div>
      <div className="content">
        <Wrapper
          apiKey={process.env.REACT_APP_GOOGLE_MAPS_API_KEY}
          render={render}
        >
          <div
            className="content-map"
            style={{ width: "100%", height: "100%" }}
          >
            <Map
              options={mapOptions}
              onCenterChange={updateCenter} // 地図の中心が変わったときに更新
              style={{ width: "100%", height: "100%" }}
            >
              <Marker position={center} />
            </Map>
            {/* 地図の中心に四角形を表示 */}
            <div className="center-square"></div>
            <div className="center-square2"></div>
          </div>
        </Wrapper>
        <button className="save-button" onClick={saveLocation}>
          この地点を指定する
        </button>
      </div>
    </div>
  );
};

/**
 * 様々な引数に基づき、googleMapを表示
 * @param {*} param0
 * @returns
 */
const Map = ({ onCenterChange, children, style, options, ...otherProps }) => {
  const ref = React.useRef(null); //dom情報を設定
  const [map, setMap] = React.useState(null); //Mapに関する各種情報を保持

  /**
   * dom情報があって、map情報がない場合、新しい地図を表示
   */
  React.useEffect(() => {
    if (ref.current && !map) {
      const newMap = new window.google.maps.Map(ref.current, options);
      setMap(newMap);
    }
  }, [ref, map, options]);

  React.useEffect(() => {
    if (map) {
      map.setOptions(options);
    }
  }, [map, options]);

  React.useEffect(() => {
    if (map) {
      window.google.maps.event.clearListeners(map, "idle");

      // `idle` イベントリスナーを設定
      map.addListener("idle", () => {
        if (onCenterChange) {
          onCenterChange(map);
        }
      });
    }
  }, [map, onCenterChange]);

  return (
    <>
      <div ref={ref} style={style} />
      {React.Children.map(children, (child) => {
        if (React.isValidElement(child)) {
          return React.cloneElement(child, { map });
        }
      })}
    </>
  );
};

const Marker = ({ position, map }) => {
  const [marker, setMarker] = React.useState(null);

  React.useEffect(() => {
    if (!marker) {
      setMarker(new window.google.maps.Marker());
    }

    return () => {
      if (marker) {
        marker.setMap(null);
      }
    };
  }, [marker]);

  React.useEffect(() => {
    if (marker && map) {
      marker.setPosition(position);
      marker.setMap(map);
    }
  }, [marker, position, map]);

  return null;
};
export default GoogleMapSelect;
```

## CSSの例
```:
/* GOogleMapSelect.css */

html, body {
  height: 100%;
  margin: 0; /* マージンをゼロに設定 */
}

#root {
  height: 100%;
  display: flex;
  align-items: center;
  justify-content: center;
}

.container {
  display: flex;
  flex-direction: column; /* 縦方向に配置 */
  width: 1000px;
  height: 1500px; /* 高さも設定することをお勧めします */
  position: relative;
  margin-top: 20px;
}

.content {
  width: 90%;
  height: 800px !important;
  margin: 10px auto;
  border: solid 1px blue;
  position: relative; 
}

.content button {
  position: absolute;
  margin: 24px 0;
  right: 0;
  border: none;
  border-radius: 5px;
  padding: 10px 20px;
  background-color: #4CAF50;
  color: white;
  font-size: 18px;
  font-weight: 600;
  cursor: pointer;
}

.map-title {
  font-size: 30px;
  font-weight: 600;
  padding: 5px 10px 0 0;
  text-align: center;
  width: 90%; /* フル幅を指定 */
  margin: 10px auto;
  background-color: blue;
  color: white;
}

.content-map {
  position: relative; /* 四角形を中央に配置するために必要 */
  width: 100%;
  height: 100%;
}

.center-square {
  position: absolute;
  width: 30px;
  height: 30px;
  background-color: transparent;
  border: solid 2px red;
  transform: translate(-50%, -50%); /* 中心に合わせるための調整 */
  top: 50%; /* 縦の中心 */
  left: 50%; /* 横の中心 */
  z-index: 10; /* 地図の上に表示されるようにする */
}
.center-square2 {
  position: absolute;
  background-color: transparent;
  width: 38px;
  height: 38px;
  border: solid 2px red;
  transform: translate(-50%, -50%); /* 中心に合わせるための調整 */
  top: 50%; /* 縦の中心 */
  left: 50%; /* 横の中心 */
  z-index: 9; /* 地図の上に表示されるようにする */
}
```


## 保存するファイルを設定
``` 　:MapComponent.js
import React, { useState } from "react";
import { APIProvider, Map, Marker } from "@vis.gl/react-google-maps";
import {
  getFirestore,
  doc,
  setDoc,
  collection,
  addDoc,
} from "firebase/firestore";
import { db } from "../firebase";

const MapComponent = () => {
  const [markerPosition, setMarkerPosition] = useState(null);

  const handleMapClick = async (event) => {
    console.log("Map click event:", event);

    const latLng = event.detail?.latLng;
    if (latLng && latLng.lat && latLng.lng) {
      const position = {
        lat: latLng.lat,
        lng: latLng.lng,
        timestamp: new Date(), // 現在の日付を追加
      };
      setMarkerPosition(position);

      try {
        // ドキュメントの ID を自動生成
        const docRef = await addDoc(collection(db, "locations"), position);
        console.log("Location saved to database with ID:", docRef.id);
      } catch (error) {
        console.error("Error saving location to database", error);
      }
    } else {
      console.error("Event does not contain latLng");
    }
  };

  return (
    <Map
      style={{ width: "100%", height: "400px" }}
      defaultCenter={{ lat: 35.658584, lng: 139.745433 }}
      defaultZoom={15}
      onClick={handleMapClick}
    >
      {markerPosition && <Marker position={markerPosition} />}
    </Map>
  );
};

export default MapComponent;

```
## 表示するファイルを設置
```:MapRedirectComponent.js
import React, { useEffect } from "react";
import { useParams } from "react-router-dom";
import { doc, getDoc } from "firebase/firestore";
import { db } from "../firebase";

const MapRedirectComponent = () => {
  const { locationId } = useParams();
  const [locationData, setLocationData] = React.useState(null);

  useEffect(() => {
    const fetchLocation = async () => {
      if (locationId) {
        try {
          const docRef = doc(db, "locations", locationId);
          const docSnap = await getDoc(docRef);

          if (docSnap.exists()) {
            setLocationData(docSnap.data());
          } else {
            console.log("No such document!");
          }
        } catch (error) {
          console.error("Error fetching location data", error);
        }
      }
    };

    fetchLocation();
  }, [locationId]);

  useEffect(() => {
    if (locationData) {
      const { lat, lng } = locationData;
      const googleMapsUrl = `https://www.google.com/maps?q=${lat},${lng}`;
      window.location.href = googleMapsUrl; // 自動的にリダイレクト
    }
  }, [locationData]);

  return <p>Redirecting...</p>; // リダイレクト中のメッセージ
};

export default MapRedirectComponent;


```

## App.js でルーティング
　react domがインストールできていること


```:  App.js
import React from "react";
import { BrowserRouter as Router, Route, Routes } from "react-router-dom";
import { APIProvider } from "@vis.gl/react-google-maps";
import MapComponent from "./components/MapComponent"; // パスを確認
import DisplayMapComponent from "./components/DisplayMapComponent"; // パスを確認

const App = () => (
  <APIProvider apiKey={process.env.REACT_APP_GOOGLE_MAPS_API_KEY}>
    <Router>
      <Routes>
        <Route path="/map" element={<MapComponent />} />
        <Route path="/displaymap" element={<DisplayMapComponent />} />
        <Route
          path="/displaymap/:locationId"
          element={<DisplayMapComponent />}
        />
        {/* 他のルートをここに追加できます */}
      </Routes>
    </Router>
  </APIProvider>
);

export default App;

```

## Home.jsを作成
```:home.js
import { collection, getDocs } from "firebase/firestore";
import React, { useEffect, useState } from "react";
import { Link } from "react-router-dom";
import { db } from "../firebase";

const Home = () => {
  const [locations, setLocations] = useState([]);

  useEffect(() => {
    const fetchLocations = async () => {
      try {
        const querySnapshot = await getDocs(collection(db, "locations"));
        const locationsData = querySnapshot.docs.map((doc) => ({
          id: doc.id,
          ...doc.data(),
        }));
        setLocations(locationsData);
      } catch (error) {
        console.error("Error fetching locations data", error);
      }
    };

    fetchLocations();
  }, []);

  return (
    <>
      <div>Home</div>
      <div>
        <Link to="/map">地図表示</Link>
      </div>
      <div>
        {locations.length > 0 ? (
          <ul>
            {locations.map((location) => (
              <li key={location.id}>
                <Link to={`/displaymap/${location.id}`}>
                  <button>{location.id}</button>
                </Link>
              </li>
            ))}
          </ul>
        ) : (
          <p>Loading location data...</p>
        )}
      </div>
    </>
  );
};

export default Home;
```

## App.jsを変更
```:App.js
import React from "react";
import { BrowserRouter as Router, Route, Routes } from "react-router-dom";
import { APIProvider } from "@vis.gl/react-google-maps";
import MapComponent from "./components/MapComponent"; // パスを確認
import DisplayMapComponent from "./components/DisplayMapComponent"; // パスを確認
import Home from "./components/Home";

const App = () => (
  <APIProvider apiKey={process.env.REACT_APP_GOOGLE_MAPS_API_KEY}>
    <Router>
      <Routes>
        <Route path="/" element={<Home />} />
        <Route path="/map" element={<MapComponent />} />
        <Route path="/displaymap" element={<DisplayMapComponent />} />
        <Route
          path="/displaymap/:locationId"
          element={<DisplayMapComponent />}
        />
        {/* 他のルートをここに追加できます */}
      </Routes>
    </Router>
  </APIProvider>
);

export default App;

```