미리보기
제가 담당한 파트로는 카카오맵으로 주변 편의점이 어디있는지 찾아주는 기능입니다.
링크 태그로 해당 지역의 카카오맵 링크를 찾아볼 수 있습니다.


준비물
https://developers.kakao.com/console
카카오계정
accounts.kakao.com
- 우선 카카오 API를 사용하기 위해서 develop홈페이지에서 플랫폼을 등록하시고,
API Key를 코드내에 적용시킵니다.
// index.html
<!-- 카카오맵 API KEY -->
<script
type="text/javascript"
src="//dapi.kakao.com/v2/maps/sdk.js?appkey=%REACT_APP_KAKAO_API_KEY%&libraries=services,clusterer"
></script>
- 가져온 Javascript 키를 index.html에 넣어줍니다. 하드코딩 하는거 보다는 .env나 .env.local 파일에 코드를 변수로 할당해놓고 사용해주는 것이 안전하다 생각했습니다.
자신의 위치 좌표 찾기
// KakaoMap.tsx
// 현재 자신의 위치 좌표를 지정해줍니다.
const setMyPosition = () => {
if (navigator.geolocation) {
navigator.geolocation.getCurrentPosition((position) => {
let lat = position.coords.latitude;
let lng = position.coords.longitude;
setMyLat(lat);
setMyLng(lng);
});
console.log('위치 수정 완료');
} else {
// 현재위치를 알 수 없는 경우, 기본 값을 설정합니다.
setMyLat(37); // 서울 위도
setMyLng(127); // 서울 경도
}
};
-JavaScript API 로 navigator.geolocation로 사용자의 위치 정보를 알아올 수 있습니다.
- 해당 getCurrentPosition() 함수는 콜백 함수를 매개 변수로 받아서 좌표를 얻어오면 position에 정보를 담아옵니다.
자신의 좌표 주변의 편의점 리스트 받아오기
// GetConvList.ts
const MAX_RESULTS = 5; // 가져올 편의점 개수
export const GetConvList = async (lat: number, lng: number): Promise<ConvsInform[]> => {
try {
const ps = new kakao.maps.services.Places();
const result = await new Promise<any>((resolve, reject) => {
ps.categorySearch(
'CS2', // 편의점
(data: any, status: any) => {
if (status === kakao.maps.services.Status.OK) {
resolve(data);
} else {
reject(new Error('편의점 검색 실패'));
}
},
{
location: new kakao.maps.LatLng(lat, lng),
useMapCenter: true,
size: MAX_RESULTS,
radius: 5000,
sort: kakao.maps.services.SortBy.DISTANCE
}
);
});
const convs: ConvsInform[] = [];
const nearestConvs: ConvsInform[] = [];
const brandArr = ['세븐일레븐', 'CU', 'GS25', '이마트24', '미니스톱'];
for (let i = 0; i < Math.min(MAX_RESULTS, result.length); i++) {
const data = result[i];
const splitedPlace = data.place_name.split(' ');
console.log(splitedPlace);
const [brand_name, position_name] = [splitedPlace[0], splitedPlace[1]];
const newConv: ConvsInform = {
position: {
lat: data.y,
lng: data.x
},
brand_name,
position_name,
full_name: data.place_name,
distance: GetDistanceBtw(lat, lng, Number(data.y), Number(data.x), 'meter')
};
convs.push(newConv);
}
for (const brand of brandArr) {
let conv = convs.find((conv) => brand === conv.brand_name);
conv !== undefined
? nearestConvs.push(conv)
: nearestConvs.push({ // 없으면 빈 배열을 넣어줍니다
brand_name: brand,
position_name: '',
full_name: '',
distance: 0,
position: { lat: 0, lng: 0 }
});
}
// console.log(nearestConvs)
return nearestConvs;
// return convs;
} catch (error) {
console.error('편의점 리스트 가져오기 오류:', error);
return [];
}
};
- <ConvsInfrom> 은 받아온 편의점 자료를 받아올 타입입니다. types>types.ts 폴더에서 가져옵니다.
-ps에 카카오 장소 제공 서비스 객체를 담아와서 사용해줍니다.
- 카테고리 코드에 맞게 장소를 제공해주는 categorySearch함수를 사용해줍니다.
- 매개변수로 결과값을 받을 콜백함수도 있고 그 안에서 데이터를 처리해줍니다.
- options: {} 매개변수로 또, options가 있는데 locaition으로 사용자의 중심 위치를 설정하고
radius로 중심에서 -- m 반경 검색, sort로 거리순으로 받아오기 옵션을 선택해줄 수 있습니다.
-편의점 리스트 원소로 distance가 있는데 사용자와 편의점의 거리를 의미합니다.
-> 이는, GetDistanceBtw 함수로 사용자 좌표, 편의점 좌표, 타입(m,kilometer)를 이용해 거리를 구해줍니다.
거리 구하는 함수
/**
* 두 지점간의 거리 계산 함수
*
* @param lat1 지점 1 위도
* @param lon1 지점 1 경도
* @param lat2 지점 2 위도
* @param lon2 지점 2 경도
* @param unit 거리 표출단위(kilometer, meter)
* @return
*/
export const GetDistanceBtw = (lat1: number, lon1: number, lat2: number, lon2: number, unit: string): number => {
/**
* 1000미만이면 소수점 없앤 정수 반환, 이상이면 km 소수점 1로 소수 반환
* @param m 미터를 기준
* @returns 정수(--)m, 소수(--.-)km number타입으로 반환
*/
const calculDistance = (m: number): number => {
let meter;
if (m < 1000) {
meter = Math.floor(m);
}
if (m >= 1000) {
meter = m / 1000;
meter = Number(meter.toFixed(1));
// if (meter % 1 === 0) meter = meter.toFixed(0);
}
return meter as number;
// meter가 정수이면 m단위, 소수점이 있으면 km단위
//(예외: 1.0km -> 1km로 바꾸는 작업 필요)
};
const deg2rad = (deg: number) => deg * (Math.PI / 180);
const rad2deg = (rad: number) => rad * (180 / Math.PI);
const theta = lon1 - lon2;
let dist =
Math.sin(deg2rad(lat1)) * Math.sin(deg2rad(lat2)) +
Math.cos(deg2rad(lat1)) * Math.cos(deg2rad(lat2)) * Math.cos(deg2rad(theta));
dist = Math.acos(dist);
dist = rad2deg(dist);
dist = dist * 60 * 1.1515;
if (unit === 'kilometer') {
dist = dist * 1.609344;
} else if (unit === 'meter') {
dist = dist * 1609.344;
}
return calculDistance(dist);
};
- 다른 블로그에서 정보를 얻어 해당 함수를 가져왔습니다.
- 좌표를 받아서 결과값이 999까지는 소수점 없는 정수로 m로 표현하기 위해서
1000이상은 소수점 첫째짜리로 만들어 반환하게 해줬습니다. km로 표현하기 위해서
랜더링
return (
<S.Container>
{nearConv && (
<>
<S.Title>지금 나랑 가장 가까운 편의점은?</S.Title>
{nearConv.distance ? (
<>
<S.LocationButton
to={`https://map.kakao.com/link/map/${nearConv?.full_name},${myLat},${myLng}`}
target="_blank"
>
<S.IconBox>
<IconMap />
</S.IconBox>
위치 보기
</S.LocationButton>
<S.NearByStore>
<S.NearByLogo> {Logo && <Logo />}</S.NearByLogo>
<S.StoreInfo>
<S.StoreName>{nearConv.position_name}</S.StoreName>
<S.Distance>
{Math.floor(nearConv.distance) === nearConv.distance
? nearConv.distance + 'm'
: nearConv.distance + 'km'}
</S.Distance>
</S.StoreInfo>
</S.NearByStore>
</>
) : (
<S.NearByStore>
<S.NoStore>{'반경 5km 내\n가까운 편의점이 없습니다.'}</S.NoStore>
</S.NearByStore>
)}
</>
)}
<S.NearByBrand>
{convs
.filter((item) => {
return item.brand_name !== nearConv?.brand_name;
})
.map((item, index) => {
return <NearByBox key={index} brand={item} />;
})}
</S.NearByBrand>
</S.Container>
);
};
- 여태해준 작업 함수를 호출해주고 값을 편의점 리스트에 저장해서 값을 적절하게 랜더링 시켜줍니다.
- 받아올 때 useEffect나 useMemo 훅을 이용하여 좌표가 변할 시에만 편의점 리스트 값을 새로 불러오게끔
해주는 과정이 중요합니다. 이렇게 안하면 순식간에 초당 300건의 자료를 불러오는 불상사가 일어납니다 ... ㅜ
작업파일
📦kakaoMap
┣ 📜GetConvList.ts
┣ 📜GetDetailAddress.tsx
┣ 📜GetDistanceBtw.ts
┗ 📜KakaoMap.tsx
참조
https://fruitdev.tistory.com/189
https://apis.map.kakao.com/web/sample/categoryFromBounds/