https://react-kakao-maps-sdk.jaeseokim.dev/
index.html에 sdk script태그 추가
<script
type="text/javascript"
src="//dapi.kakao.com/v2/maps/sdk.js?appkey=발급받은 APP KEY를 넣으시면 됩니다.&libraries=services,clusterer"
></script>
install
npm install react-kakao-maps-sdk
or
yarn add react-kakao-maps-sdk
지도 생성하기
import import { Map } from "react-kakao-maps-sdk";
export default function KakaoMap(){
return (
<Map // 지도를 표시할 Container
center={{
// 지도의 중심좌표
lat: 33.450701,
lng: 126.570667,
}}
style={{
// 지도의 크기
width: "100%",
height: "450px",
}}
level={3} // 지도의 확대 레벨
/>
);
}
나의 경우 음식점들만 지도에 나타내야했는데 react-kakao-maps-sdk에서 제공하는 api와 kakao developers에서 제공하는 api와는 차이가 있어서 카테고리별 검색 기능을 찾지 못해 처음에는 키워드 검색을 기준으로 map api를 구현하였다.
import React, { useState, useEffect } from "react";
import { Map, MapMarker, CustomOverlayMap } from "react-kakao-maps-sdk";
function(){
const { kakao } = window;
const [info, setInfo] = useState()
const [markers, setMarkers] = useState([])
const [map, setMap] = useState()
useEffect(() => {
if (!map) return
const ps = new kakao.maps.services.Places()
ps.keywordSearch("이태원 맛집", (data, status, _pagination) => {
if (status === kakao.maps.services.Status.OK) {
// 검색된 장소 위치를 기준으로 지도 범위를 재설정하기위해
// LatLngBounds 객체에 좌표를 추가합니다
const bounds = new kakao.maps.LatLngBounds()
let markers = []
for (var i = 0; i < data.length; i++) {
// @ts-ignore
markers.push({
position: {
lat: data[i].y,
lng: data[i].x,
},
content: data[i].place_name,
})
// @ts-ignore
bounds.extend(new kakao.maps.LatLng(data[i].y, data[i].x))
}
setMarkers(markers)
// 검색된 장소 위치를 기준으로 지도 범위를 재설정합니다
map.setBounds(bounds)
}
})
}, [map])
return (
<Map // 로드뷰를 표시할 Container
center={{
lat: 37.566826,
lng: 126.9786567,
}}
style={{
width: "100%",
height: "350px",
}}
level={3}
onCreate={setMap}
>
{markers.map((marker) => (
<MapMarker
key={`marker-${marker.content}-${marker.position.lat},${marker.position.lng}`}
position={marker.position}
onClick={() => setInfo(marker)}
>
{info &&info.content === marker.content && (
<div style={{color:"#000"}}>{marker.content}</div>
)}
</MapMarker>
))}
</Map>
)
}
위 검색어 입력 지도 생성하기 sample에 input을 추가해 이태원 맛집 항목에 input의 value로 변경해 주었다.
const { kakao } = window;
const [info, setInfo] = useState()
const [markers, setMarkers] = useState([])
const [map, setMap] = useState()
const [searchInputValue, setSearchInputValue] = useState("");
const [keyword, setKeyword] = useState("");
useEffect(() => {
if (!map) return
const ps = new kakao.maps.services.Places()
ps.keywordSearch(`${keyword} 음식점`, (data, status, _pagination) => {
if (status === kakao.maps.services.Status.OK) {
// 검색된 장소 위치를 기준으로 지도 범위를 재설정하기위해
// LatLngBounds 객체에 좌표를 추가합니다
const bounds = new kakao.maps.LatLngBounds()
let markers = []
for (var i = 0; i < data.length; i++) {
markers.push({
position: {
lat: data[i].y,
lng: data[i].x,
},
content: data[i].place_name,
})
bounds.extend(new kakao.maps.LatLng(data[i].y, data[i].x))
}
setMarkers(markers)
// 검색된 장소 위치를 기준으로 지도 범위를 재설정합니다
map.setBounds(bounds)
}
})
}, [map, keyword])
const handleKeyPress = (e) => {
if (e.key === "Enter") setKeyword(searchInputValue);
};
...
return (
<...>
<SearchInput
onChange={(e) => setSearchInputValue(e.target.value)}
onKeyPress={(e) => handleKeyPress(e)}
value={searchInputValue}
placeholder={"주소를 입력해주세요 ex)강남역 or 서울특별시 역삼동"}
/>
<SearchButton onClick={() => setKeyword(searchInputValue)}>
검색
</SearchButton>
<...>
)
이런 형식으로 keyword
상태가 업데이트 되면 useEffect 안의 내용이 다시 실행 되게끔하고 검색어도 ${keyword} 음식점
이라는 형식으로 음식점만을 검색하게 만들었다.
검색결과는 ps.keywordSearch의 data 파라미터로 받아 볼 수 있다.
하지만 뭔가 만족스럽지 못하다. https://react-kakao-maps-sdk.jaeseokim.dev/ 해당 사이트에 카테고리별 검색에 대한 안내가 없지만 기본적으로 kakao developers에서 서비스중인 api를 베이스로 만든 것일 텐데 기능이 생각보다 너무 적다.
그래서 모듈안에 있는 Map 컴포넌트와 keywordSearch 함수에 대한 코드를 살펴봤다. 잘 읽어보면 사이트 내 api나 sample 항목에서 미처 설명못한 기능들을 구현할 방법들이 생각날거다.
Map 컴포넌트
/// <reference types="kakao.maps.d.ts" />
/// <reference types="kakao.maps.d.ts" />
/// <reference types="kakao.maps.d.ts" />
import React from "react";
import { PolymorphicComponentPropsWithOutRef } from "../types";
export declare const KakaoMapContext: React.Context<kakao.maps.Map>;
export declare type MapProps = {
/**
* 중심으로 설정할 위치 입니다.
*/
center: {
lat: number;
lng: number;
} | {
x: number;
y: number;
};
/**
* 중심을 이동시킬때 Panto를 사용할지 정합니다.
* @default false
*/
isPanto?: boolean;
/**
* 중심 좌표를 지정한 좌표 또는 영역으로 부드럽게 이동한다. 필요하면 확대 또는 축소도 수행한다.
* 만약 이동할 거리가 지도 화면의 크기보다 클 경우 애니메이션 없이 이동한다.
* padding 만큼 제외하고 영역을 계산하며, padding 을 지정하지 않으면 기본값으로 32가 사용된다.
*/
padding?: number;
/**
* 확대 수준 (기본값: 3)
*/
level?: number;
/**
* 최대 확대 수준
*/
maxLevel?: number;
/**
* 최소 확대 수준
*/
minLevel?: number;
/**
* 지도 종류 (기본값: 일반 지도)
*/
mapTypeId?: kakao.maps.MapTypeId;
/**
* 마우스 드래그, 휠, 모바일 터치를 이용한 시점 변경(이동, 확대, 축소) 가능 여부
*/
draggable?: boolean;
/**
* 마우스 휠이나 멀티터치로 지도 확대, 축소 기능을 막습니다. 상황에 따라 지도 확대, 축소 기능을 제어할 수 있습니다.
*/
zoomable?: boolean;
/**
* 마우스 휠, 모바일 터치를 이용한 확대 및 축소 가능 여부
*/
scrollwheel?: boolean;
/**
* 더블클릭 이벤트 및 더블클릭 확대 가능 여부
*/
disableDoubleClick?: boolean;
/**
* 더블클릭 확대 가능 여부
*/
disableDoubleClickZoom?: boolean;
/**
* 투영법 지정 (기본값: kakao.maps.ProjectionId.WCONG)
*/
projectionId?: string;
/**
* 지도 타일 애니메이션 설정 여부 (기본값: true)
*/
tileAnimation?: boolean;
/**
* 키보드의 방향키와 +, – 키로 지도 이동,확대,축소 가능 여부 (기본값: false)
*/
keyboardShortcuts?: boolean | {
/**
* 지도 이동 속도
*/
speed: number;
};
/**
* map 생성 후 해당 객체를 전달하는 함수
*/
onCreate?: (map: kakao.maps.Map) => void;
/**
* 중심 좌표가 변경되면 발생한다.
*/
onCenterChanged?: (target: kakao.maps.Map) => void;
/**
* 확대 수준이 변경되기 직전 발생한다.
*/
onZoomStart?: (target: kakao.maps.Map) => void;
/**
* 확대 수준이 변경되면 발생한다.
*/
onZoomChanged?: (target: kakao.maps.Map) => void;
/**
* 지도 영역이 변경되면 발생한다.
*/
onBoundsChanged?: (target: kakao.maps.Map) => void;
/**
* 지도를 클릭하면 발생한다.
*/
onClick?: (target: kakao.maps.Map, mouseEvent: kakao.maps.event.MouseEvent) => void;
/**
* 지도를 더블클릭하면 발생한다.
*/
onDoubleClick?: (target: kakao.maps.Map, mouseEvent: kakao.maps.event.MouseEvent) => void;
/**
* 지도를 마우스 오른쪽 버튼으로 클릭하면 발생한다.
*/
onRightClick?: (target: kakao.maps.Map, mouseEvent: kakao.maps.event.MouseEvent) => void;
/**
* 지도에서 마우스 커서를 이동하면 발생한다.
*/
onMouseMove?: (target: kakao.maps.Map, mouseEvent: kakao.maps.event.MouseEvent) => void;
/**
* 드래그를 시작할 때 발생한다.
*/
onDragStart?: (target: kakao.maps.Map, mouseEvent: kakao.maps.event.MouseEvent) => void;
/**
* 드래그를 하는 동안 발생한다.
*/
onDrag?: (target: kakao.maps.Map, mouseEvent: kakao.maps.event.MouseEvent) => void;
/**
* 드래그가 끝날 때 발생한다.
*/
onDragEnd?: (target: kakao.maps.Map, mouseEvent: kakao.maps.event.MouseEvent) => void;
/**
* 중심 좌표나 확대 수준이 변경되면 발생한다.
* 단, 애니메이션 도중에는 발생하지 않는다.
*/
onIdle?: (target: kakao.maps.Map) => void;
/**
* 확대수준이 변경되거나 지도가 이동했을때 타일 이미지 로드가 모두 완료되면 발생한다.
* 지도이동이 미세하기 일어나 타일 이미지 로드가 일어나지 않은경우 발생하지 않는다.
*/
onTileLoaded?: (target: kakao.maps.Map) => void;
/**
* 지도 기본 타일(일반지도, 스카이뷰, 하이브리드)이 변경되면 발생한다.
*/
onMaptypeidChanged?: (target: kakao.maps.Map) => void;
children?: React.ReactNode | undefined;
};
declare type MapComponent = <T extends React.ElementType = "div">(props: PolymorphicComponentPropsWithOutRef<T, MapProps> & React.RefAttributes<kakao.maps.Map>) => React.ReactElement | null;
/**
* 기본적인 Map 객체를 생성하는 Comeponent 입니다.
* props로 받는 `on*` 이벤트는 해당 `kakao.maps.Map` 객체를 함께 인자로 전달 합니다.
*
* `ref`를 통해 `map` 객체에 직접 접근하여 사용 또는 onCreate 이벤트를 이용하여 접근이 가능합니다.
*
* > *주의 사항* `Map`, `RoadView` 컴포넌트에 한하여, ref 객체가 컴포넌트 마운트 시점에 바로 초기화가 안될 수 있습니다.
* >
* > 컴포넌트 마운트 시점에 `useEffect` 를 활용하여, 특정 로직을 수행하고 싶은 경우 `ref` 객체를 사용하는 것보다
* > `onCreate` 이벤트와 `useState`를 함께 활용하여 제어하는 것을 추천 드립니다.
*/
declare const Map: MapComponent;
export default Map;
keywordSearch
/// <reference path="index.d.ts" />
declare namespace kakao.maps.services {
/**
* 장소 검색 서비스.
*
* @see [Places](http://apis.map.kakao.com/web/documentation/#services_Places)
*/
export class Places {
/**
* 장소 검색 서비스 객체를 생성한다.
*
* @param map 중심 좌표를 Places 객체의 location으로 설정할 지도 객체
*/
constructor(map?: Map);
/**
* 지도 객체를 설정한다. 이미 설정되어 있는 지도는 `setMap(null)` 로 해제 가능하다.
*
* @param map 지도 객체
*/
public setMap(map: Map | null): void;
/**
* 입력한 키워드로 검색한다.
*
* @param keyword 검색할 키워드
* @param callback 검색 결과를 받을 콜백함수
* @param options
*/
public keywordSearch(
keyword: string,
callback: (
result: PlacesSearchResult,
status: Status,
pagination: Pagination
) => void,
options?: PlacesSearchOptions
): void;
/**
* 주어진 카테고리 코드로 검색한다.
* 카테고리 검색은 영역 검색이 기본이므로
* 옵션에 명세된 `x`, `y` 또는 `rect` 를 직접 지정하거나,
* `location` 또는 `bounds` 값을 넣어 주어야 한다.
* 아니면 지정한 `Map` 객체를 이용하는 옵션인 `useMapCenter` 또는
* `useMapBounds` 을 참으로 설정하여 지도의 영역이 자동으로
* 관련 값에 할당되도록 해도 된다.
*
* @param code 검색할 카테고리 코드
* @param callback 검색 결과를 받을 콜백함수
* @param options
*/
public categorySearch(
code: CategoryCode | `${CategoryCode}`,
callback: (
result: PlacesSearchResult,
status: Status,
pagination: Pagination
) => void,
options?: PlacesSearchOptions
): void;
}
export type PlacesSearchResult = PlacesSearchResultItem[];
export interface PlacesSearchResultItem {
/**
* 장소 ID
*/
id: string;
/**
* 장소명, 업체명
*/
place_name: string;
/**
* 카테고리 이름
* 예) 음식점 > 치킨
*/
category_name: string;
/**
* 중요 카테고리만 그룹핑한 카테고리 그룹 코드
* 예) FD6
*/
category_group_code?: `${CategoryCode}` | `${Exclude<CategoryCode, "">}`[];
/**
* 중요 카테고리만 그룹핑한 카테고리 그룹명
* 예) 음식점
*/
category_group_name: string;
/**
* 전화번호
*/
phone: string;
/**
* 전체 지번 주소
*/
address_name: string;
/**
* 전체 도로명 주소
*/
road_address_name: string;
/**
* X 좌표값 혹은 longitude
*/
x: string;
/**
* Y 좌표값 혹은 latitude
*/
y: string;
/**
* 장소 상세페이지 URL
*/
place_url: string;
/**
* 중심좌표까지의 거리(x,y 파라미터를 준 경우에만 존재). 단위 meter
*/
distance: string;
}
export interface PlacesSearchOptions {
/**
* 키워드 필터링을 위한 카테고리 코드
*/
category_group_code?: `${CategoryCode}` | `${Exclude<CategoryCode, "">}`[];
/**
* 중심 좌표. 특정 지역을 기준으로 검색한다.
*/
location?: LatLng;
/**
* x 좌표, longitude, `location` 값이 있으면 무시된다.
*/
x?: number;
/**
* y 좌표, latitude, `location` 값이 있으면 무시된다.
*/
y?: number;
/**
* 중심 좌표로부터의 거리(반경) 필터링 값. `location` / `x`, `y` / `useMapCenter` 중 하나와 같이 써야 의미가 있음. 미터(m) 단위. 기본값은 5000, 0~20000까지 가능
*/
radius?: number;
/**
* 검색할 사각형 영역
*/
bounds?: LatLngBounds;
/**
* 사각 영역. 좌x,좌y,우x,우y 형태를 가짐. `bounds` 값이 있으면 무시된다.
*/
rect?: string;
/**
* 한 페이지에 보여질 목록 개수. 기본값은 15, 1~15까지 가능
*/
size?: number;
/**
* 검색할 페이지. 기본값은 1, `size` 값에 따라 1~45까지 가능
*/
page?: number;
/**
* 정렬 옵션. `DISTANCE` 일 경우 지정한 좌표값에 기반하여 동작함. 기본값은 `ACCURACY` (정확도 순)
*/
sort?: SortBy;
/**
* 지정한 Map 객체의 중심 좌표를 사용할지의 여부. 참일 경우, `location` 속성은 무시된다. 기본값은 false
*/
useMapCenter?: boolean;
/**
* 지정한 Map 객체의 영역을 사용할지의 여부. 참일 경우, `bounds` 속성은 무시된다. 기본값은 false
*/
useMapBounds?: boolean;
}
}
역시나 생각했던 기능들이 다 들어있었다.
굳이 ${keyword} 음식점
으로 검색할 필요없이 장소만 검색어로 전달하고 keywordSearch 함수의 options 항목으로 음식점 코드(FD6)을 전달해주면 음식점만을 검색하게 된다.
// ...생략
const options = {
category_group_code: "FD6", // 음식점만 검색한다
page : 2, // 2페이지의 검색 결과를 받는다
};
ps.keywordSearch(
searchInputValue,
(data, status, _pagination) => {},
options
);
// ...생략
map drag 재검색도 kakao developes 참고해서
useEffect(() => {
var geocoder = new kakao.maps.services.Geocoder();
geocoder.coord2Address(position.lng, position.lat, displayCenterInfo);
}, [position]);
function displayCenterInfo(result, status) {
if (status === kakao.maps.services.Status.OK) {
let detailAddr = !!result[0].road_address
? result[0].road_address.address_name
: "";
detailAddr += result[0].address.address_name;
setKeyword(detailAddr);
}
}
위와같이 중심좌표를 먼저 구한다음 행정주소로 변환해서 keyword를 업데이트하는 방식으로 복잡하게 구현했었는데 그럴필요없이 Map 컴포넌트의
<Map
onDragEnd={(map) => {
setPosition({
lat: map.getCenter().getLat(),
lng: map.getCenter().getLng(),
});
>
...
</Map>
onDragEnd 이벤트로 중심좌표를 업데이트하고 keywordSearch options의 location 속성을 이용해보면 쉽게 수정이 가능 할 것 같다.
결론 : 좀 더 빨리 생각했으면 두번 고생 안할텐데..