오늘은 현재 위치의 주소를 표시하는 작업을 수정해보려한다.

위 이미지상 보이는 빨간 테두리처럼 현재 위치를 주소로 받아와야 한다.
현재 위치를 주소로 받아오기 위한 구체적인 방법을 생각해보면 카카오맵 open API의 coord2Address 방법을 사용해야 하고, 카카오맵 자체에는 현재 좌표를 받아오는 메소드가 없기 때문에 Geolocation api를 사용하여 현재 위치를 좌표로 받아와야 한다.
따라서 흐름상 Geolocation api를 사용하여 좌표를 받아오고 그 좌표를 카카오맵 open API를 통해 지번 주소로 변환해야 한다.
Geolocation을 통해 현재 좌표를 받아오는 것은 현재 생각으로는 소음지도 NavBar가 활성화되었을때도 사용될 것으로 예상하기 때문에 커스텀 훅으로 생성할 것이다.
현재 좌표를 받아오는 것은 geolocation의 getCurrentPosition 메서드를 사용해야 한다.
Geolocation API는 navigator.geolocation을 통해 접근할 수 있으며 이때 브라우저는 유저에게 위치 정보 접근 권한을 요청하고 동의할 경우 위치정보를 가져올 수 있다.
위치정보를 가져오는 방법은 크게 2가지로
1. geolocation.getCurrentposition : 장소의 현재 위치를 가져온다.
2. geolocation.watchPosition : 장치의 위치가 바뀔 때마다, 자동으로 새로운 위치를 사용해 호출할 처리기 함수를 등록한다.
소음 프로젝트의 경우 일정 공간내에서 소음을 측정해야하기 때문에 실시간 위치변화를 측정할 필요가 없어 getCurrentPosition을 사용할 예정이고 간단한 테스트로 성공반환값에 무엇이 들어오는지 테스트를 진행하였다.
getCurrentPosition 메서드의 매개변수로는 최대 3개까지 받을 수 있으며 필수로 받는 매개변수는 위치 정보를 성공적으로 가져온 경우 호출될 콜백함수이다. 나는 성공시 콜백으로 어떠한 값이 반환되는지는 console로 출력해보았다.
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
</head>
<body>
<div id="coordinate"></div>
</body>
<script>
navigator.geolocation.getCurrentPosition((position)=>{
console.log(position)
})
</script>
</html>
위 코드의 실행 결과 아래 이미지와 같은 결과가 출력되었다. 여기서 우리가 사용할 값은 coords에서도 latitude(위도)와 longitude(경도)이다.

이것을 React의 커스텀 훅함수로 변경해보면
import { useState, useEffect } from 'react';
const useCurrentLocation = () => {
const [coords, setCoords] = useState(null);
useEffect(() => {
navigator.geolocation.getCurrentPosition(
(position) => {
const { latitude, longitude } = position.coords;
setCoords({ latitude, longitude });
},
(err) => {
setError('위치를 가져오는 데 실패했습니다.');
console.error(err);
}
);
}
}, []);
return coords;
};
export default useCurrentLocation;
이렇게 나타낼 수 있을 것이라 생각했다.
해당 훅함수를 사용한 컴포넌트가 초기 랜더링시 getCurrentPosition이 실행되어야 하고, 해당 좌표값, 즉 lat, lng은 useState의 coords에 할당되고, coords를 반환함으로써 좌표를 얻을 수 있다. 다만, chatGPT는 다른 커스텀 훅 함수를 제안하였는데
import { useState, useEffect } from 'react';
interface Coordinates {
latitude: number;
longitude: number;
}
const useCurrentLocation = () => {
const [coords, setCoords] = useState<Coordinates | null>(null);
const [error, setError] = useState<string | null>(null);
useEffect(() => {
if (navigator.geolocation) {
navigator.geolocation.getCurrentPosition(
(position) => {
const { latitude, longitude } = position.coords;
setCoords({ latitude, longitude });
},
(err) => {
setError('위치를 가져오는 데 실패했습니다.');
console.error(err);
}
);
} else {
setError('브라우저가 위치 서비스를 지원하지 않습니다.');
}
}, []);
return { coords, error };
};
export default useCurrentLocation;
이 코드를 보았을 때 if(navigator.geolocation)의 의미자체가 특정 브라우저에서 navigator.geolocation이 지원된다면 실행되도록 설정한 것으로 보였다.
다만 MDN 문서를 확인해보면

모든 브라우저에서 지원되는 것으로 보인다.
이에 따라 해당 커스텀 훅함수가 과도하게 공수가 들어간 것이 아닐까라는 생각이 들었지만, 안전성을 확보한다는 점에서 사용하는 것이 좀 더 바람직할 것 같다는 생각이 들어 해당 훅함수를 사용하기로 하였다.
이제 이 좌표를 받아와서 주소로 변경하는 작업을 진행해야 한다.
카카오맵 open api 공식 문서를 살펴보면 좌표 -> 주소로 변환하는 메서드는 coord2Address가 존재한다. 카카오맵 공식 문서 - coord2Address를 보면 좀 더 정확한 사용법을 확인할 수 있다.
공식문서의 사용법에 근거하여 이를 커스텀 훅 함수로 변환하면
import { useState, useEffect } from 'react';
const useCoordinateToAddress = (coords: { latitude: number; longitude: number } | null) => {
const [address, setAddress] = useState<string | null>(null);
const [error, setError] = useState<string | null>(null);
useEffect(() => {
if (!coords) return;
const fetchAddress = async () => {
try {
const kakao = window.kakao;
const geocoder = new kakao.maps.services.Geocoder();
const coord = new kakao.maps.LatLng(coords.latitude, coords.longitude);
geocoder.coord2Address(coord.getLng(), coord.getLat(), (result, status) => {
if (status === kakao.maps.services.Status.OK) {
const address = result[0].address.address_name;
setAddress(address);
} else {
setError('주소를 변환하는 데 실패했습니다.');
}
});
} catch (e) {
setError('카카오맵 API 호출 중 오류가 발생했습니다.');
console.error(e);
}
};
fetchAddress();
}, [coords]);
return { address, error };
};
export default useCoordinateToAddress;
해당 코드에 대해 잠시 설명하자면 위의 useCurrentLocation 함수에서 반환된 coordds 값이 존재하지 않는다면 조기 return되고, coords값이 변화할때 좌표->주소 변환 메서드가 실행되도록 구성되었다. 사용법에 따라 new kakao.maps.services.Geocoder()를 통해 주소-좌표간 변환 서비스 객체를 생성하고 coords를 통해 가져온 Lat과 Lng을 기반으로 coord2Address를 통해 변환된 도로명 주소를 콜백 함수로 반환받는다.
이제 위에서 만든 것들을 사용하여 화면에 표시하는 작업을 진행할 것이다.
import React, { useEffect, useState } from 'react';
import Logo from '../../assets/logo/logo.svg';
import Info from '../../assets/icons/ico_Info.png';
import { useDispatch } from 'react-redux';
import { ChartContainer, Container, Header, InfoWrapper, LogoWrapper } from './Noise.styles';
import { toggleModal } from '../../store/menu/menuSlice';
import DateTimeDisplay from '../../component/time/DateTimeDisplay';
import useCurrentLocation from '../../hook/useCurrentLocation';
import useCoordinateToAddress from '../../hook/useCoordinateToAddress';
import AddressDisplay from '../../component/currentLocate/AddressDisplay';
import useResetStateOnPath from '../../hook/useResetStateOnPath';
import { useAppSelector } from '../../hook/redux';
import { RootState } from '../../store';
const Noise = () => {
const [currentDate, setCurrentDate] = useState(new Date());
const { coords, error: locationError } = useCurrentLocation();
const { address, error: addressError } = useCoordinateToAddress(coords);
const { isFixed, fixedDate } = useAppSelector((state: RootState) => state.dateTime);
useResetStateOnPath('/measure');
useEffect(() => {
if (!isFixed) {
const intervalId = setInterval(() => {
setCurrentDate(new Date());
}, 1000);
return () => clearInterval(intervalId);
}
}, [isFixed]);
const displayDate = isFixed && fixedDate ? fixedDate : currentDate;
const dispatch = useDispatch();
return (
<Container>
<Header>
<LogoWrapper>
<img src={Logo} alt='logo'/>
</LogoWrapper>
<InfoWrapper onClick={() => dispatch(toggleModal(true))}>
<img src={Info} alt='info'/>
</InfoWrapper>
</Header>
<ChartContainer>
<div>
<DateTimeDisplay date={displayDate}/>
<AddressDisplay address={address} locationError={locationError} addressError={addressError} />
</div>
{/* <DecibelChart/> */}
</ChartContainer>
</Container>
);
};
export default Noise;
import React from 'react';
interface AddressDisplayProps {
address: string | null;
locationError: string | null;
addressError: string | null;
}
const AddressDisplay: React.FC<AddressDisplayProps> = ({ address, locationError, addressError }) => {
if (locationError) {
return <div>현재 위치를 가져올 수 없습니다: {locationError}</div>;
}
if (addressError) {
return <div>주소 변환 오류: {addressError}</div>;
}
if (address) {
return <div>현재 위치: {address}</div>;
}
return <div>위치를 불러오는 중...</div>;
};
export default AddressDisplay;
이렇게 진행하면 아래와 같은 결과를 얻을 수 있다.

이제 해당 위치를 figma에서 디자인한 것과 동일하게 변경해야 하고 시간과 주소를 한줄에 위치시키기 위해 주소 문자열을 편집하여준다. '도'와 '시' 보다는 '동'과 번지가 나오는 것이 구체적인 느낌을 줄 것 같아 공백을 기준으로 자른뒤 배열의 마지막 요소에서부터 마지막에서 2번째 인덱스 요소까지 선택하도록 코드를 수정하여 준다.
import { useState, useEffect } from 'react';
const useCoordinateToAddress = (coords: { latitude: number; longitude: number } | null) => {
const [address, setAddress] = useState<string | null>(null);
const [error, setError] = useState<string | null>(null);
useEffect(() => {
if (!coords) return;
const fetchAddress = async () => {
try {
const kakao = window.kakao;
const geocoder = new kakao.maps.services.Geocoder();
const coord = new kakao.maps.LatLng(coords.latitude, coords.longitude);
geocoder.coord2Address(coord.getLng(), coord.getLat(), (result, status) => {
if (status === kakao.maps.services.Status.OK) {
const fullAddress = result[0].address.address_name;
const addressParts = fullAddress.split(' '); // 공백으로 나누기
const trimmedAddress = addressParts.slice(-2).join(' '); // 뒤에서 두 항목만 결합
setAddress(trimmedAddress);
} else {
setError('주소를 변환하는 데 실패했습니다.');
}
});
} catch (e) {
setError('카카오맵 API 호출 중 오류가 발생했습니다.');
console.error(e);
}
};
fetchAddress();
}, [coords]);
return { address, error };
};
export default useCoordinateToAddress;
이렇게 커스텀 훅함수를 수정해주고 스타일링을 적용하면

위와 같이 예쁘게 스타일링이 이루어진다.
물론 이런 번지로 나타내는 것보다는 일반 주소가 사람들에게 익숙하다는 생각이 있었다. 이를 사용하기 위해서는 행정안전부에서 제공하는 도로명 주소 API가 필요하며 이를 통해 도로명 주소를 다시 일반 주소로 변경할 수는 있다. 그러나 API key 발급시 도메인 주소가 필요한데 현재 화면단이 변경사항이 많아 어떻게 변할지 모르기 때문에 망설여졌고, 이미 개인적으로 너무 많은 외부 api들을 다루고 있다는 생각이 들어 다른 기능들을 완성한 후에 다뤄볼 생각이다.