[Next.js] 주소 입력 → 위/경도 출력 → 지도 출력 (카카오 geocoder/지도 api)
본문 바로가기
더 알아보기/기능

[Next.js] 주소 입력 → 위/경도 출력 → 지도 출력 (카카오 geocoder/지도 api)

by 은돌1113 2023. 1. 10.

구현하려고 한 기능은 사용자가 주소 입력 시 위/경도를 계산해서 지도에 출력하는 기능이었다.

구글링을 해봐도 카카오/구글 developer로 이동해서 Html/Js/Css로 나오고 React 관련되어서 나오길래

여러 블로그를 테스트 해보다가 Next.js에 적용했던 과정을 기록했다.

 

Next.js..... kakao 지도 관련 글이 어떻게 이렇게 없지😢

 

1. 카카오 지도 Api Key 발급 받기

주소를 위/경도로 변환해주는 기능을 Geocoding, Geocoder라고 한다. 구글링할 때 카카오 geocoder라고 치면 나온다.

 

카카오지도 API 키 발급 받는 방법

1. 카카오 개발자 사이트에 접속 https://developers.kakao.com/ 카카오 개발 사이트에서 “시작하기” 혹은 “로그인” 버튼을 클릭합니다. 2. 애플리케이션 생성 애플리케이션을 추가합니다. 앱 아이콘

sorrow16.tistory.com

 

2. 주소 입력 👉 위/경도 출력하기

https://apis.map.kakao.com/web/sample/addr2coord/

 

3. 카카오 지도 생성 (+ 확대/축소, 원래 위치로 이동하기)

 

지도 생성하기 | react-kakao-maps-sdk docs

지도를 생성하는 가장 기본적인 예제입니다.

react-kakao-maps-sdk.jaeseokim.dev

 

+ 구현 중 오류 발생 시 참고

  • Cannot read property 'Geocoder' of undefined error 발생 시
 

[kakao map지도 api] 에러 : Cannot read property 'Geocoder' of undefined

[kakao map지도 api] 에러 : Cannot read property 'Geocoder' of undefined ~~ 에러 발생 주...

blog.naver.com

  • Api Key 환경변수 설정하는 방법
 

Next js 환경변수

NEXT JS Environment Variables를 번역했습니다. Next.js 버전 9.4 이상용입니다. 이전 버전의 Next.js를 사용하는 경우 next.config.js에서 환경 변수를 업그레이드하거나 참조하세요. (github.com/vercel/next.js/tree/cana

taenami.tistory.com

  • 브라우저 참고용 : NEXT_PUBLIC_
 

[Next.js] 환경변수 사용 방법: .env파일 (development, production, local)

# Next.js 환경변수(Environment Variables) 사용 방법 Next.js는 아래의 3가지 방식으로 환경변수 기능을 지원한다. 1. process.env.NODE_ENV : 구동환경 체크용 환경변수 2. .env 파일 : 구동 환경별 환경변수 적용

curryyou.tistory.com

 

4. 구현 화면

 

+ 구현 코드 (JS/CSS)

더보기
import { useEffect, useState } from "react";
import { CustomOverlayMap, Map, MapMarker } from "react-kakao-maps-sdk";

import AddIcon from "@mui/icons-material/Add";
import MinusIcon from "@mui/icons-material/Remove";
import RefreshIcon from "@mui/icons-material/Refresh";

const Address = () => {
    const [state, setState] = useState({
        // 지도의 초기 위치
        center: { lat: 33.452613, lng: 126.570888 },
        // 부드럽게 이동할 지 유무
        isPanto: true,
        // 검색한 주소
        address: "경기도 부천시 중동 1143-3 KR",
        // 지도 레벨
        level: 3,
    });

    const [address, setAddress] = useState("경기도 부천시 중동 1143-3 KR"); // 임시
    const [submit, setSubmit] = useState(false); // 임시
    const [open, setOpen] = useState(true); // overlay 오픈 유무

    const mapBtnObj = {
        plus: () => {
            setState((prev) => {
                return {
                    ...prev,
                    level: prev.level + 1,
                };
            });
        },
        minus: () => {
            setState((prev) => {
                return {
                    ...prev,
                    level: prev.level - 1,
                };
            });
        },
        reset: () => {
            // 지도 중심 좌표로 이동 시킬 때 타입과 숫자가 같으면 동작을 안해서 누를 때마다 타입 확인 후 변경 해줘서 기능 구현
            const latType = typeof state.center.lat;
            const lngType = typeof state.center.lng;

            setState((prev) => {
                return {
                    ...prev,
                    center: {
                        lat:
                            latType === "number"
                                ? String(prev.center.lat)
                                : Number(prev.center.lat),
                        lng:
                            lngType === "number"
                                ? String(prev.center.lng)
                                : Number(prev.center.lng),
                    },
                    level: 3,
                };
            });
        },
    };

    useEffect(() => {
        kakaoMapGeoCoder();
        setSubmit(false);
    }, [submit]);

    const kakaoMapGeoCoder = () => {
        window.kakao.maps.load(() => {
            // 주소-좌표 변환 객체를 생성합니다
            const geocoder = new window.kakao.maps.services.Geocoder();

            // 주소로 좌표를 검색합니다
            geocoder.addressSearch(address, function (result, status) {
                // 정상적으로 검색이 완료됐으면
                if (status === window.kakao.maps.services.Status.OK) {
                    setState((prev) => {
                        return {
                            ...prev,
                            center: {
                                lat: Number(result[0].y),
                                lng: Number(result[0].x),
                            },
                            isPanto: !prev.isPanto,
                        };
                    });
                }
            });
        });
    };

    return (
        <>
            <div className="mapWrap">
                <Map
                    className="map"
                    center={state.center}
                    isPanto={state.isPanto}
                    level={state.level} // 지도의 확대 레벨
                >
                    <MapMarker position={state.center}></MapMarker>
                    <CustomOverlayMap position={state.center} yAnchor={1}>
                        <div className="customoverlay">
                            <a
                                href={`https://map.kakao.com/link/map/${state.address},${state.center.lat},${state.center.lng}`}
                                target="_blank"
                                rel="noreferrer"
                            >
                                <span className="title">{state.address}</span>
                            </a>
                        </div>
                    </CustomOverlayMap>
                </Map>
                <div className="mapBtnWrap">
                    <button
                        className="mapBtn"
                        onClick={() => {
                            mapBtnObj["minus"]();
                        }}
                    >
                        <AddIcon />
                    </button>
                    <button
                        className="mapBtn"
                        onClick={() => {
                            mapBtnObj["plus"]();
                        }}
                    >
                        <MinusIcon />
                    </button>
                    <br />
                    <button
                        className="mapBtn"
                        onClick={() => {
                            mapBtnObj["reset"]();
                        }}
                    >
                        <RefreshIcon />
                    </button>
                </div>
                {open && (
                    <div
                        className="pointer"
                        onClick={() => {
                            setOpen((prev) => {
                                return !prev;
                            });
                        }}
                    >
                        <span className="openMapText">지도를 보려면 클릭하세요.</span>
                        <div className="mapOverlay"></div>
                    </div>
                )}
            </div>

            {/* 아래 부분 임시 */}
            <div style={{ marginTop: "600px" }}>
                <p>주소를 입력 해주세요.</p>
                <input
                    type="text"
                    value={address}
                    onChange={(e) => {
                        setAddress(e.target.value);
                    }}
                />
                <p>위도: {state.center.lat}</p>
                <p>경도: {state.center.lng}</p>
                <button
                    onClick={() => {
                        setState((prev) => {
                            return { ...prev, address: address };
                        });
                        setSubmit(true);
                    }}
                >
                    저장
                </button>
            </div>
        </>
    );
};

export default Address;

 

/* 폰트 변경 할 때 */
@import url("https://cdn.jsdelivr.net/gh/orioncactus/pretendard@v1.3.5/dist/web/static/pretendard.css");

body {
    margin: 0;
    padding: 0 15px;
}

body * {
    font-family: "Pretendard";
    word-break: keep-all;
    white-space: pre-line;
    transition: all 0.5s;
}

.pointer {
    cursor: pointer;
}

.mapWrap {
    position: static;
}

.map {
    width: calc(100% - 30px);
    height: 547px;
    border-radius: 12px;
    position: absolute;
    top: 0;
    left: 15px;
}

.map .customoverlay a {
    position: absolute;
    z-index: 3;
    top: -73px;
    left: 50%;
    transform: translateX(-50%);
    border: 1px solid #e5e5e5;
}

.map .customoverlay a:before {
    position: absolute;
    left: 0;
    right: 0;
    bottom: -17px;
    width: 17px;
    height: 18px;
    margin: auto;
    background: url("..//public/img/mapArrowBottom.png") no-repeat;
    content: "";
}

.map .customoverlay span {
    display: inline-block;
    padding: 4px 8px 4px;
    border-radius: 3px;
    font-weight: 600;
    font-size: 11px;
    line-height: 15px;
    background: white;
    color: #000;
    z-index: 10;
    white-space: nowrap;
}

.mapBtnWrap {
    position: absolute;
    top: 200px;
    right: 25px;
    z-index: 11;
}

.mapBtn {
    width: 30px;
    height: 30px;
    display: flex;
    align-items: center;
    justify-content: center;
    background: white;
    border-radius: 8px;
    border: 1px solid #e5e5e5;
    cursor: pointer;
    margin: 2px 0px;
}

.mapBtn:hover {
    background: #e5e5e5;
}

.openMapText {
    position: absolute;
    top: 20px;
    left: 35px;
    font-weight: 500;
    color: #fff;
    z-index: 13;
}

.mapOverlay {
    width: calc(100% - 30px);
    height: 387px;
    position: absolute;
    top: 0;
    left: 15px;
    background: #000;
    opacity: 0.3;
    color: #fff;
    padding-top: 160px;
    text-align: center;
    z-index: 12;
    border-radius: 12px;
}

댓글