프로젝트 ing
병원 키오스크 연습을 위한 웹앱..같은걸 만들고 있다
이번에 만들 기능은 주변 약국 찾기 기능.
단계가 지금 보니 좀 애매하게 나눠져 있는 것 같아서 + 그리고 경로안내를... 뭘 어케하지?? 싶어가지고 일단 원하는 약국을 표시하고 - 선택해 정보를 보는 것부터 만들어보기로 했다.
이미 어느정도 진행이 된 프로젝트라 기본 스타일, 껍데기 같은 건 다 구현되어 있으니 지도를 불러오는 것부터 시작해보자
카카오맵 API를 사용했다
공식 문서 https://apis.map.kakao.com/web/documentation/
1. 카카오 개발자 등록하고 애플리케이션 추가하기
API 사용을 위해 앱을 등록해주자
앱을 등록하면 콘솔의 '앱 키' 탭에서 APP키를 확인할 수 있다. 리액트로 개발중이니 자바스크립트 키를 가져와서 사용하면 된다.
플랫폼 탭에서 웹사이트 도메인(지금은 개발ing라서 http://localhost:3000로)까지 등록해주면 준비 끝
2. 프로젝트에 API 가져오기
일단 앱 키는 유출되면 안 되니까 .env파일에 따로 저장해주고 .gitignore에 추가해주자.
루트 경로에 .env 만들고, REACT_APP_ 접두사를 붙여 환경변수 세팅. 따옴표는 붙이지 않는다
이제 public/index.html에 script로 API를 불러와야 한다.
<script
type="text/javascript"
src="//dapi.kakao.com/v2/maps/sdk.js?appkey=<%= process.env.REACT_APP_KAKAO_MAP_KEY%>&libraries=services"
></script>
나는 장소검색이 필요해 services 라이브러리를 함께 가져왔다.
무튼 head 안에 이런 태그를 넣으면 세팅은 끝
이 모든 과정은 공식 문서에 아주 친절히 나와있으니 참고하자.
https://apis.map.kakao.com/web/guide/
3. 지도 불러오기
시작하기 전에.. 타입스크립트에서 kakao객체를 사용하기 위해 전역 객체를 확장해주고, 일일이 window.kakao 쓰기 귀찮으니 구조분해 할당을 먼저 해주자
declare global {
interface Window {
kakao: any;
}
}
const { kakao }: any = window;
map객체는 new kakao.maps.Map(container, options) 와 같이 선언한다.
이때 container은 맵을 그릴 DOM이고 options는 지도 생성에 사용할 기본 옵션이다. 지도의 중심, 확대 레벨 등이 있다.
const mapRef = useRef<HTMLDivElement>(null);
const [map, setMap] = useState<any>();
useEffect(() => {
if (mapRef.current) {
const container = mapRef.current; // 지도를 표시할 div
const options = {
center: new kakao.maps.LatLng(33.450701, 126.570667), // 지도의 중심좌표
level: 5, // 지도의 확대 레벨
};
const initMap = new kakao.maps.Map(container, options); // 지도 생성 및 객체 리턴
setMap(initMap); // state에 map 객체 저장
}
}, []);
<div> 같이 HTML태그를 직접 써서 컨테이너를 만들어줘도 되는데 (공식문서에 예시로 잘 나와있다) 나는 맵을 보여줄 컨테이너를 그냥 따로 styled Component로 빼고, useRef를 통해 연결해줬다.
이런 식으로
return (
<MapWrapper>
<MapContainer ref={mapRef}></MapContainer>
</MapWrapper>
);
스타일은 생략
그럼 이제 기본 세팅의 지도 불러오기가 완료된다. 대충 다 만들어놓고 기록을 시작해서 캡쳐는 없다..
4. 내 위치로 지도 세팅하기
현재 위치를 가져오기 위해 navigator의 geolocation, getCurrentPosition 함수를 사용한다. 현재 위치의 위도와 경도를 받아와 그걸 카카오맵에서 사용할 수 있는 위치 객체로 만들어준 뒤, 지도의 중심을 바꿔주자.
useEffect(() => {
if (map) {
//현재 위치 받아오기
if (navigator.geolocation) {
navigator.geolocation.getCurrentPosition(function (position) {
const lat = position.coords.latitude;
const lon = position.coords.longitude;
const locPosition = new kakao.maps.LatLng(lat, lon);
setCurLoc(locPosition); //현재위치를 변수에 세팅(다른 함수에서 사용)
map.setCenter(locPosition); //현재위치 받아서 지도의 중심으로 설정
현재 위치는 경로표시를 위해 다른 곳에서도 쓸 것이므로 미리 useState를 사용해 curLoc이라는 변수를 만들어뒀다
아무튼 현재 위치를 가져왔으면 setCenter을 사용해 지도 중심을 바꿔주자. 위치 정보 사용을 허용해주고 새로고침하면 원래 카카오 본사 중심이었던 지도가 내 위치를 중심으로 세팅될 것 :P
5. 내 위치 마커 표시하기
마커를 놓아 내 위치를 시각적으로 볼 수 있게 해주자.
https://apis.map.kakao.com/web/documentation/#Marker
공식 문서는 신이니까 그냥 이것만 봐도 된다. 마커 객체를 생성하고 포지션을 받아온 현재 위치로 세팅하면 기본 마커가 나타나는데, 나는 다른 위치에도 마커를 많이 찍을 거니까 구분하기 위해 현재 위치 마커를 병원 마크 이미지로 바꿔주었다. MarkerImage 객체에 이미지경로와 사이즈 등을 세팅할 수 있다.
//현재위치 마커 객체
const curMarker = new kakao.maps.Marker({
map: map,
position: locPosition,
image: new kakao.maps.MarkerImage(curImageSrc, imageSize),
});
curMarker.setMap(map); //현재위치 마커 세팅
6. 주변 약국 검색하고 결과 가져오기
본론이다. services 라이브러리의 keywordSearch 기능을 이용한다.
공식문서에 나와있다. 한국어 도큐먼트 너무 아름답다
내가 영어권 개발자였다면 세상이 이지모드였을 텐데 아쉽다.
https://apis.map.kakao.com/web/documentation/#services_Places_keywordSearch
useEffect(() => {
const ps = new kakao.maps.services.Places(); //키워드서치를 위한 place 라이브러리 사용
ps.keywordSearch('약국', searchCallback, { location: curLoc }); //현재위치 기준으로 '약국'검색
}, [curLoc]);
이게다임
시뮬레이션 웹이니 현재 위치가 바뀔 일이야 딱히 없겠지만 아무튼 현재위치에 따라 검색결과는 달라져야 하니 useEffect를 사용해줬다.
검색 키워드는 '약국'
searchCallback은 검색결과를 받아올 콜백함수, location은 검색의 기준이 될 위치이다.
searchCallback 함수를 만들어주자
그 전에.. api 콜을 해서 한번 응답 결과를 받아보면, 배열 형태로 15개 정도의 결과값이 오는 것을 볼 수 있는데
각 결과값은 이런 필드를 가진 객체이다.
일단 찾은 장소들의 위치에 마커를 찍어 표시하는 것부터 해보자. 그러려면 위치가 필요할 테니 x와 y 속성을 갖고오면 될 것이다.
function searchCallback(result: any, status: any) {
if (status === kakao.maps.services.Status.OK) {
result.forEach((loc: any) => {
//각 응답의 x, y위치를 가져와 마커로 표시
const locX = loc.x;
const locY = loc.y;
const markerLoc = new kakao.maps.LatLng(locY, locX); //로케이션 세팅
const marker = new kakao.maps.Marker({
//각 위치의 마커 객체
map: map,
position: markerLoc,
image: new kakao.maps.MarkerImage(pharImageSrc, imageSize),
clickable: true,
});
marker.setMap(map); //맵에 마커 추가
타입은 자꾸 빨간줄떠서 any로 막아놨다. 이런..
여튼 공식문서에 따르면 callback arguments로는 result, status, pagination이 있다고 하니 앞에 두개를 받아와주고, status가 OK인지 즉 응답이 잘 왔는지 조건문으로 세팅해준다.
result 배열이 잘 왔다면, 안에 있는 객체 하나하나에 대해 마커를 찍어줄 것이므로 forEach로 객체 하나씩 만져주자. 각 객체에서 x와 y를 뽑아오고, 카카오맵에서 쓰는 로케이션 객체인 kakao.maps.LatLng를 (위도경도라 x y가 반대다) 해당 위치로 세팅해준 뒤, 해당 position을 가진 마커 객체를 만들어 setMap 해주면
귀엽고 아름다운 마커가 잘 찍히는 것을 볼 수 있다.
7. 마커에 정보 표시하기 (오버레이)
위치만 표시하면 뭐함... 약국 이름 정도는 띄워줘야 어딘지 알지
마커를 표시하면서 이벤트 리스너를 같이 붙여주자.
공식 문서에도 Events가 따로 잘 나와있다.
마커에 마우스를 올리면 현재 위치는 현재 위치로, 약국 마커는 해당 약국 이름을 띄워주도록 하자.
원래는 이걸 다른 컴포넌트로 빼서 마우스오버 여부에 따라 display를 조정하고 안에 text를 바꿔주고 할 생각이었는데 문제가 있었다.
마커의 x, y를 아니까 그걸 가져와서 position에 사용하면 되겠지? 생각을 했는데, 이게 상대위치가 아니라 진짜 위도/경도라서 위치별로 값 차이가 거의 안 난다. 126.56xxx 126.55xxxx 이난리인데 반영이 될 리가 없지.....
그래서 이걸 우짜지 하고 문서를 뒤져보니 이미 카카오맵 API에 아주 좋은 기능이 들어 있었다.
https://apis.map.kakao.com/web/documentation/#CustomOverlay
커스텀 오버레이라고 지도 위에 html문자열 또는 dom element를 설정해 올릴 수 있다. 있는 걸 쓰는 게 훨씬 나으니까 오버레이를 만들어 올려보자.
//커스텀 오버레이 - 각 마커에 마우스오버하면 장소 이름을 띄움
const overlay = new kakao.maps.CustomOverlay({
position: new kakao.maps.LatLng(33.450701, 126.570667), //초기위치
yAnchor: 1.3,
clickable: true,
});
일단 선언 시에는 기본 위치로 초기 세팅하고, yAnchor은 마커에서 y방향으로 얼마나 떨어진 위치에 표시할지를 정해줄 수 있다!
위치는 됐고, 오버레이의 내용은 content라는 필드에 html문자열을 넣어줘야 하는데... 나는 여기에 자세히 보기 버튼까지 들어가야 하니까 생각보다 문자열이 길어져서, 그냥 오버레이 content를 생성하는 함수를 따로 뺐다.
function makeOverlayContent({
placeName,
id,
}: {
placeName: string;
id: string;
}) {
const nextUrl = `/pharmacy/2/${id}`; //동적 url로 각 위치마다 해당id 페이지로 이동할 수 있게
return `
<div style="
padding: 10px;
background-color: white;
border: 1px solid black;
border-radius: 5px;
box-shadow: 0px 4px 8px rgba(0, 0, 0, 0.1);
">
<div>${placeName}</div>
<button id="detailButton" onClick="window.location.href='${nextUrl}'" style="
margin-top: 5px;
padding: 5px 10px;
background-color: #007bff;
color: white;
border: none;
border-radius: 3px;
cursor: pointer;
z-index: 3;
">
자세히 보기
</button>
</div>
`;
}
기본 스타일이라 지피티가 써줬다... ㅎㅎ땡큐...
자세히 보기 버튼을 누르면 해당 약국의 정보를 소개하는 페이지로 넘어가게 하고 싶어서, onClick을 사용해줬다. html문자열로 사용해서 그런지 winidow.location.href를 사용해줘야 한다고 한다....
소소하게 중요했던 건 버튼에 z-index를 줘서 위로 확 올려줘야 클릭이 잘 된다. 안 하면 뭐 닌자시험보듯 눌러야 한다
이제 searchCallback으로 돌아가 마커 세팅한 뒤 마커에 이벤트리스너를 붙여주자.
//...생략
//각 마커에 마우스 이벤트 추가(오버레이 보이기/숨기기)
kakao.maps.event.addListener(marker, 'mouseover', function () {
const newContent = makeOverlayContent({
placeName: loc.place_name,
id: loc.id,
});
overlay.setPosition(markerLoc);
overlay.setContent(newContent);
overlay.setMap(map);
});
//마우스 치워도 창이 바로 사라지지 않게 타이머 설정
kakao.maps.event.addListener(marker, 'mouseout', function () {
setTimeout(() => {
overlay.setMap(null);
}, 1000);
});
여기서도 소소한 팁으로... 오버레이가 어느정도 마커 위에 겹쳐지다 보니 마우스를 올림 -> 오버레이가 뜸 -> 마우스가 오버레이 위에 올라가 mouseout이 실행됨 -> 오버레이 꺼짐. 이런 현상이 발생했었다. 버튼을 누를 시간이 도저히 없었던 것...
그래서는 안 되니까 타이머를 설정해줘서 마우스가 마커를 벗어나더라도 1초간은 오버레이가 유지되도록 만들었다.
현재 위치도 '현재 위치'라는 오버레이가 뜰 수 있게 똑같은 방식으로 세팅해주면 된다. 단 이때는 id도 placeName도 필요 없으니 그냥 content에 직접 html 문자열을 써 줘도 상관없다.~
이렇게 대략적인 지도 기능을 구현해보았다.
이제 '자세히 보기' 를 눌렀을 때 나올 페이지와, 마커 클릭 시 경로안내가 되도록 해야 한다(...) 일단 직선 경로는 나오게 설정해뒀는데 어차피 수정할 거라 이건 다음에 더 쓰는 걸로
아무튼 첫 페이지는 대충 이렇게 완성~
'기록' 카테고리의 다른 글
[에이닷(A.)] 하이 멀티LLM, 잼얘해줘! (5) | 2024.09.19 |
---|---|
[Flutter] OpenAI Text to speech로 챗봇 채팅 음성으로 읽어주기(tts) (0) | 2024.09.11 |
[Flutter] speech_to_text로 음성 인식 구현하기 (1) | 2024.09.03 |
레오니드 팬 페이지 만들기(2) (0) | 2024.02.23 |
레오니드 팬 페이지 만들기 (1) (0) | 2024.02.20 |