๐Ÿ—บ๏ธ React + Google Maps + GeoJSON์œผ๋กœ ๋Œ€ํ•œ๋ฏผ๊ตญ ์‹œ๋„๋ณ„ ์ƒ‰์ƒ ์‹œ๊ฐํ™”ํ•˜๊ธฐ

GoogleMap

๋ชฉ๋ก ๋ณด๊ธฐ
1/5

์•ˆ๋…•ํ•˜์„ธ์š”!
์ด๋ฒˆ ํฌ์ŠคํŒ…์—์„œ๋Š” Google Maps API๋ฅผ ์‚ฌ์šฉํ•ด์„œ ๋Œ€ํ•œ๋ฏผ๊ตญ์˜ ์‹œ๋„(์„œ์šธ, ๋ถ€์‚ฐ ๋“ฑ)๋ฅผ GeoJSON ๋ฐ์ดํ„ฐ ๊ธฐ๋ฐ˜์œผ๋กœ ๋งต์— ๊ทธ๋ฆฌ๊ณ , ๊ฐ ์‹œ๋„๋ณ„๋กœ ์„œ๋กœ ๋‹ค๋ฅธ ์ƒ‰์ƒ์„ ์ž…ํ˜€๋ณด๋Š” ๊ณผ์ •์„ ์ •๋ฆฌํ•ด๋ณด์•˜์Šต๋‹ˆ๋‹ค.

ํ”„๋ก ํŠธ์—”๋“œ ๊ธฐ์ˆ  ์Šคํƒ์€ React + TypeScript์ด๋ฉฐ, ์ง€๋„ ์œ„์— ๋ฐ์ดํ„ฐ๋ฅผ ์‹œ๊ฐํ™”ํ•˜๋Š” ๊ฐ„๋‹จํ•œ ์˜ˆ์ œ๋ฅผ ๋งŒ๋“ค์–ด๋ดค์Šต๋‹ˆ๋‹ค.


๐ŸŽฏ ๋ชฉํ‘œ

  • Google Maps๋ฅผ React์—์„œ ์‚ฌ์šฉํ•˜๊ธฐ
  • GeoJSON์„ ๊ธฐ๋ฐ˜์œผ๋กœ ์ง€๋„์— ํ–‰์ •๊ตฌ์—ญ ํ‘œ์‹œํ•˜๊ธฐ
  • ๊ฐ ์‹œ๋„๋งˆ๋‹ค ๋‹ค๋ฅธ ์ƒ‰์ƒ ์ž…ํžˆ๊ธฐ
  • ๋งˆ์šฐ์Šค ์˜ค๋ฒ„์‹œ ์Šคํƒ€์ผ ๋ณ€ํ™” ์ ์šฉํ•˜๊ธฐ

๐Ÿ“ฆ ์‚ฌ์šฉํ•œ ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ

npm install @react-google-maps/api

@react-google-maps/api๋Š” Google Maps๋ฅผ React์—์„œ ์‰ฝ๊ฒŒ ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ๋„๋ก ๋„์™€์ฃผ๋Š” ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ์ž…๋‹ˆ๋‹ค.


๐Ÿงฉ GeoJSON ๋ฐ์ดํ„ฐ ์ค€๋น„

GeoJSON์€ ๋‹ค์Œ๊ณผ ๊ฐ™์ด ์ƒ๊ธด ํ˜•ํƒœ๋กœ, ๊ฐ ์‹œ๋„๋ณ„ ๊ฒฝ๊ณ„ ์ •๋ณด๋ฅผ ๋‹ด๊ณ  ์žˆ์–ด์š”.

{
  "type": "Feature",
  "geometry": {
    "type": "Polygon",
    "coordinates": [...]
  },
  "properties": {
    "CTP_KOR_NM": "๋Œ€๊ตฌ๊ด‘์—ญ์‹œ",
    "CTP_ENG_NM": "Daegu"
  }
}

properties.CTP_KOR_NM ์†์„ฑ์„ ํ™œ์šฉํ•ด์„œ ์ƒ‰์ƒ์„ ์ง€์ •ํ•ฉ๋‹ˆ๋‹ค.


๐Ÿงช ๊ตฌํ˜„ ์ฝ”๋“œ

// components/KoreaMap.tsx
import { GoogleMap, LoadScript } from "@react-google-maps/api";
import { useRef } from "react";

const containerStyle = {
  width: "100%",
  height: "100vh",
};

const center = {
  lat: 36.5,
  lng: 127.5,
};

const regionColors: Record<string, string> = {
  ์„œ์šธํŠน๋ณ„์‹œ: "#e41a1c",
  ๋ถ€์‚ฐ๊ด‘์—ญ์‹œ: "#377eb8",
  ๋Œ€๊ตฌ๊ด‘์—ญ์‹œ: "#4daf4a",
  ์ธ์ฒœ๊ด‘์—ญ์‹œ: "#984ea3",
  ๊ด‘์ฃผ๊ด‘์—ญ์‹œ: "#ff7f00",
  ๋Œ€์ „๊ด‘์—ญ์‹œ: "#ffff33",
  ์šธ์‚ฐ๊ด‘์—ญ์‹œ: "#a65628",
  ์„ธ์ข…ํŠน๋ณ„์ž์น˜์‹œ: "#f781bf",
  ๊ฒฝ๊ธฐ๋„: "#999999",
  ๊ฐ•์›ํŠน๋ณ„์ž์น˜๋„: "#66c2a5",
  ์ถฉ์ฒญ๋ถ๋„: "#fc8d62",
  ์ถฉ์ฒญ๋‚จ๋„: "#8da0cb",
  ์ „๋ผ๋ถ๋„: "#e78ac3",
  ์ „๋ผ๋‚จ๋„: "#a6d854",
  ๊ฒฝ์ƒ๋ถ๋„: "#ffd92f",
  ๊ฒฝ์ƒ๋‚จ๋„: "#e5c494",
  ์ œ์ฃผํŠน๋ณ„์ž์น˜๋„: "#b3b3b3",
};

export default function KoreaMap({ geoJson }: { geoJson: any }) {
  const mapRef = useRef<google.maps.Map | null>(null);

  const onLoad = (map: google.maps.Map) => {
    mapRef.current = map;

    map.data.addGeoJson(geoJson);

    map.data.setStyle((feature) => {
      const name = feature.getProperty("CTP_KOR_NM");
      const fillColor =
        typeof name === "string" && name in regionColors
          ? regionColors[name]
          : "#cccccc";

      return {
        fillColor,
        strokeColor: "#555",
        strokeWeight: 1,
        fillOpacity: 0.6,
      };
    });

    map.data.addListener("mouseover", (event: any) => {
      map.data.overrideStyle(event.feature, { fillColor: "#FEB24C" });
    });

    map.data.addListener("mouseout", () => {
      map.data.revertStyle();
    });
  };

  return (
    <LoadScript googleMapsApiKey={process.env.REACT_APP_GOOGLE_MAPS_API_KEY!}>
      <GoogleMap
        mapContainerStyle={containerStyle}
        center={center}
        zoom={7}
        onLoad={onLoad}
      />
    </LoadScript>
  );
}

์ฐธ๊ณ : .env ํŒŒ์ผ์— Google Maps API Key๋ฅผ ๋„ฃ์–ด์ฃผ๊ณ  REACT_APP_GOOGLE_MAPS_API_KEY=... ํ˜•์‹์œผ๋กœ ์„ค์ •ํ•ด์•ผ ํ•ฉ๋‹ˆ๋‹ค.


๐Ÿคฏ ํƒ€์ž…์Šคํฌ๋ฆฝํŠธ ์ด์Šˆ ํ•ด๊ฒฐ

์ฒ˜์Œ์—” ์ด๋Ÿฐ ์—๋Ÿฌ๊ฐ€ ๋ฐœ์ƒํ–ˆ์–ด์š”:

TS2538: Type 'unknown' cannot be used as an index type.

์ด๋Š” feature.getProperty("CTP_KOR_NM")์˜ ๋ฐ˜ํ™˜ ํƒ€์ž…์ด unknown์ด๊ธฐ ๋•Œ๋ฌธ์ด์—ˆ์Šต๋‹ˆ๋‹ค.
์ด๋ฅผ ํ•ด๊ฒฐํ•˜๊ธฐ ์œ„ํ•ด typeof name === "string"์œผ๋กœ ๋จผ์ € ์ฒดํฌํ•œ ๋‹ค์Œ,
name in regionColors๋กœ ํ‚ค ์กด์žฌ ์—ฌ๋ถ€๋ฅผ ํ™•์ธํ•ด ํƒ€์ž… ์•ˆ์ •์„ฑ์„ ํ™•๋ณดํ–ˆ์Šต๋‹ˆ๋‹ค.

if (typeof name === "string" && name in regionColors) {
  const fillColor = regionColors[name]; // ์•ˆ์ „!
}

๐ŸŒˆ ๊ฒฐ๊ณผ ํ™”๋ฉด

  • ๋Œ€ํ•œ๋ฏผ๊ตญ ์‹œ๋„๊ฐ€ ์ง€๋„์— ํ‘œ์‹œ๋จ
  • ๊ฐ ์‹œ๋„๋งˆ๋‹ค ์ง€์ •ํ•œ ์ƒ‰์ƒ์œผ๋กœ ๊ตฌ๋ถ„๋จ
  • ๋งˆ์šฐ์Šค๋ฅผ ์˜ฌ๋ฆฌ๋ฉด fillColor๊ฐ€ ๋ณ€๊ฒฝ๋˜์–ด ํ•˜์ด๋ผ์ดํŠธ ํšจ๊ณผ


๐Ÿ’ก ๋ฐฐ์šด ์ 

  • Google Maps API์—์„œ map.data.addGeoJson()์œผ๋กœ GeoJSON์„ ์ง์ ‘ ์ถ”๊ฐ€ํ•  ์ˆ˜ ์žˆ๋‹ค.
  • map.data.setStyle()์€ Feature๋ณ„๋กœ ์Šคํƒ€์ผ์„ ์ปค์Šคํ„ฐ๋งˆ์ด์ง•ํ•  ์ˆ˜ ์žˆ๋Š” ์œ ์šฉํ•œ ๊ธฐ๋Šฅ!
  • value is Type ํ˜•ํƒœ์˜ ํƒ€์ž… ๊ฐ€๋“œ ํ•จ์ˆ˜๋กœ TypeScript์—์„œ๋„ ์•ˆ์ „ํ•œ GeoJSON ํ•ธ๋“ค๋ง์ด ๊ฐ€๋Šฅ
  • in ์—ฐ์‚ฐ์ž๋Š” ๊ฐ์ฒด์˜ ํ‚ค ์กด์žฌ ์—ฌ๋ถ€๋ฅผ ์•ˆ์ „ํ•˜๊ฒŒ ์ฒดํฌํ•˜๋Š” ๋ฐ ๊ผญ ํ•„์š”ํ•จ

๐Ÿงฉ ๋‹ค์Œ ๋ชฉํ‘œ

  • ์‹œ๋„ ํด๋ฆญ ์‹œ ํ•ด๋‹น ์ง€์—ญ ์ •๋ณด ํ‘œ์‹œ
  • ์‹œ๊ฐํ™”์— animation ํšจ๊ณผ ์ถ”๊ฐ€
  • ๊ณต๊ณต๋ฐ์ดํ„ฐ API์™€ ์—ฐ๋™ํ•˜์—ฌ ์‹ค์‹œ๊ฐ„ ๋ฐ์ดํ„ฐ ๋ฐ˜์˜ํ•˜๊ธฐ

๐Ÿ™Œ ๋งˆ๋ฌด๋ฆฌ

์ง€๋„ ์‹œ๊ฐํ™”๋Š” ์ƒ๊ฐ๋ณด๋‹ค ์žฌ๋ฏธ์žˆ๊ณ , ํ™œ์šฉ๋„๊ฐ€ ๋†’์€ ์ž‘์—…์ด์—ˆ์Šต๋‹ˆ๋‹ค.
ํŠนํžˆ GeoJSON์„ ์–ด๋–ป๊ฒŒ ํ™œ์šฉํ•˜๋А๋ƒ์— ๋”ฐ๋ผ, ํ–‰์ •๊ตฌ์—ญ ์‹œ๊ฐํ™”๋ถ€ํ„ฐ ๋ฐ์ดํ„ฐ ๊ธฐ๋ฐ˜ ๋ถ„์„๊นŒ์ง€ ๋‹ค์–‘ํ•œ ํ”„๋กœ์ ํŠธ๋กœ ํ™•์žฅํ•  ์ˆ˜ ์žˆ๊ฒ ๋‹ค๋Š” ๊ฐ€๋Šฅ์„ฑ์„ ๋А๊ผˆ์–ด์š”.

๊ถ๊ธˆํ•œ ์ ์ด๋‚˜ ๋„์›€์ด ํ•„์š”ํ•˜์‹  ๋ถ„์€ ๋Œ“๊ธ€๋กœ ๋‚จ๊ฒจ์ฃผ์„ธ์š”!
์ฝ์–ด์ฃผ์…”์„œ ๊ฐ์‚ฌํ•ฉ๋‹ˆ๋‹ค โ˜บ๏ธ

profile
'ํ•ด์ธ์‚ฌํŒ”๋งŒ๊ธฐ๋ก'์€ ํ”„๋ก ํŠธ์—”๋“œ ๊ฐœ๋ฐœ์˜ ์œ ์ตํ•œ ์ฃผ์ œ์™€ ๋‚ด์šฉ์„ ๊ธฐ๋กํ•˜์—ฌ ๊ณต์œ ํ•˜๋Š” ๊ณต๊ฐ„์ž…๋‹ˆ๋‹ค.

0๊ฐœ์˜ ๋Œ“๊ธ€