티스토리 뷰
2026.05.12 - [API] - Google Maps API 구글 지도 API 연동 - 2. 길찾기
Step 1에서 지도를 띄우고, Step 2에서 경로를 그렸다면, 이제는 사용자가 가고 싶은 곳을 스스로 찾게 할 차례입니다. 구글 지도의 진정한 힘은 방대한 '데이터베이스'에 있습니다. 전 세계 수억 개의 장소 정보를 내 서비스에 그대로 가져와 보겠습니다.
본 포스팅에서는 Places API를 활용하여 검색창 자동완성(Autocomplete)을 구현하고, 특정 장소의 전화번호, 영업시간, 리뷰 등의 상세 정보를 불러오는 방법을 상세히 다룹니다.
1. Places API: 지도에 생명력을 불어넣는 데이터 엔진
Places API는 단순히 '검색' 기능만 제공하는 것이 아닙니다. 구글이 보유한 고품질의 장소 데이터를 네 가지 핵심 기능으로 제공합니다.
- Place Autocomplete (자동완성): 사용자가 입력을 시작하면 실시간으로 추천 장소를 제안합니다. 오타 교정 및 근처 장소 우선순위 노출이 가능합니다.
- Place Details (장소 상세 정보): 특정 장소의 주소, 전화번호, 사용자 평점, 리뷰, 웹사이트, 영업 상태 등을 가져옵니다.
- Place Photos (장소 사진): 구글 사용자들이 업로드한 고해상도 장소 사진에 접근합니다.
- Nearby Search (주변 검색): 현재 위치 주변의 '카페', '주유소' 등 특정 카테고리의 장소를 찾습니다.
2. 사전 준비: 라이브러리 로드 방식의 변화
Places API는 기본 Maps JavaScript API와는 별도의 라이브러리 뭉치로 관리됩니다. 따라서 스크립트를 불러올 때 libraries=places라는 파라미터를 반드시 추가해야 합니다.
<!-- libraries=places 파라미터 추가 필수! -->
<script async src="https://maps.googleapis.com/maps/api/js?key=YOUR_API_KEY&libraries=places&callback=initMap"></script>
3. 실전 구현: 검색 자동완성(Autocomplete) 적용하기
가장 대중적으로 사용되는 '검색창' UI를 만들어 보겠습니다. 구글은 이를 위해 google.maps.places.Autocomplete 객체를 제공합니다.
3.1 전체 예제 코드 (HTML/JS)
이 코드는 검색창을 생성하고, 장소를 선택하면 해당 위치로 지도를 이동시킨 뒤 마커와 함께 장소 정보를 팝업으로 띄워줍니다.
<!DOCTYPE html>
<html lang="ko">
<head>
<meta charset="UTF-8">
<title>Google Places API 실전 가이드</title>
<style>
body { margin: 0; font-family: 'Pretendard', sans-serif; display: flex; flex-direction: column; height: 100vh; }
#search-container { padding: 20px; background: #fff; box-shadow: 0 2px 10px rgba(0,0,0,0.1); z-index: 10; }
#pac-input { width: 100%; max-width: 500px; padding: 12px 15px; border: 2px solid #4285F4; border-radius: 25px; outline: none; font-size: 16px; }
#map { flex: 1; width: 100%; }
#infowindow-content { display: none; }
#map #infowindow-content { display: inline; }
.place-title { font-weight: bold; font-size: 1.1em; color: #1a73e8; }
.place-address { color: #555; margin-top: 5px; }
</style>
</head>
<body>
<div id="search-container">
<input id="pac-input" type="text" placeholder="장소, 주소 또는 맛집 검색">
</div>
<div id="map"></div>
<!-- 정보창(InfoWindow) 내부 템플릿 -->
<div id="infowindow-content">
<span id="place-name" class="place-title"></span><br>
<span id="place-address" class="place-address"></span><br>
<span id="place-rating"></span>
</div>
<script async src="https://maps.googleapis.com/maps/api/js?key=YOUR_API_KEY&libraries=places&callback=initMap&language=ko"></script>
<script>
function initMap() {
const map = new google.maps.Map(document.getElementById("map"), {
center: { lat: 37.5665, lng: 126.9780 },
zoom: 13,
mapTypeControl: false,
});
const input = document.getElementById("pac-input");
const infowindow = new google.maps.InfoWindow();
const infowindowContent = document.getElementById("infowindow-content");
infowindow.setContent(infowindowContent);
const marker = new google.maps.Marker({
map: map,
anchorPoint: new google.maps.Point(0, -29),
});
// 1. 자동완성 객체 생성
const autocomplete = new google.maps.places.Autocomplete(input, {
fields: ["address_components", "geometry", "name", "formatted_address", "rating"],
strictBounds: false,
types: ["establishment"], // '장소' 위주로 검색 (주소만 원하면 'address')
});
// 2. 검색창과 지도를 동기화 (사용자가 보고 있는 화면 위주로 검색)
autocomplete.bindTo("bounds", map);
// 3. 사용자가 목록에서 장소를 선택했을 때의 이벤트
autocomplete.addListener("place_changed", () => {
infowindow.close();
marker.setVisible(false);
const place = autocomplete.getPlace();
if (!place.geometry || !place.geometry.location) {
window.alert("검색 결과가 없습니다: '" + place.name + "'");
return;
}
// 지도 위치 이동 및 확대
if (place.geometry.viewport) {
map.fitBounds(place.geometry.viewport);
} else {
map.setCenter(place.geometry.location);
map.setZoom(17);
}
// 마커 위치 설정
marker.setPosition(place.geometry.location);
marker.setVisible(true);
// 정보창 데이터 바인딩
infowindowContent.children["place-name"].textContent = place.name;
infowindowContent.children["place-address"].textContent = place.formatted_address;
infowindowContent.children["place-rating"].textContent = place.rating ? `평점: ⭐ ${place.rating}` : "";
infowindow.open(map, marker);
});
}
</script>
</body>
</html>
Places API는 구글 지도 서비스 중 가장 비용이 높은 편에 속합니다. 특히 Place Details는 요청하는 데이터의 양에 따라 과금 체계가 세분화됩니다.
4.1 'Fields' 파라미터 활용 (필수!)
위의 코드에서 fields: ["address_components", "geometry", "name", ...] 부분을 보셨나요? 구글은 기본적으로 모든 데이터를 가져오려 하지만, 개발자가 필요한 필드만 명시적으로 요청하면 불필요한 데이터(예: 리뷰, 영업시간 등 비싼 데이터)에 대한 비용 발생을 막을 수 있습니다.
- 기본 데이터 (무료): 주소 정보, 위도/경도, 아이콘 등
- 접촉 데이터 (유료): 전화번호, 웹사이트 주소, 영업 상태
- 분위기 데이터 (가장 비쌈): 사용자 리뷰, 평점, 사진
4.2 세션 토큰 (Session Tokens)
자동완성 기능 사용 시, 글자 하나를 칠 때마다 API 호출이 발생하면 비용이 기하급수적으로 늘어납니다. 구글은 '한 번의 검색 시퀀스'를 하나의 세션으로 묶어 단일 요금으로 청구하는 방식을 제공합니다. Autocomplete 위젯을 사용할 경우 내부적으로 자동 관리되지만, 직접 API를 호출할 때는 반드시 sessiontoken을 포함해야 합니다.
5. 한국 시장에서의 Places API 특이점
- 국내 주소 체계: 구글은 도로명 주소와 지번 주소를 모두 지원하지만, 국내 데이터 갱신 속도는 네이버나 카카오 지도에 비해 다소 늦을 수 있습니다.
- 전화번호 포맷: 한국 국가 번호(+82)가 포함된 국제 규격으로 반환되는 경우가 많으므로, UI 노출 시 정규식을 이용한 가공이 필요할 수 있습니다.
- 장소 사진: 국내 식당이나 카페의 사진 데이터는 구글 지도 사용자(Local Guides)에 의해 생성되므로, 데이터 양은 충분하나 퀄리티가 편향될 수 있습니다.
6. 에러 핸들링 및 디버깅
- REQUEST_DENIED: API 콘솔에서 Places API가 활성화되었는지, 그리고 키 제한 사항에 Places API 권한이 포함되어 있는지 확인하세요.
- 검색 결과 부족: componentRestrictions: { country: 'kr' } 옵션을 추가하여 한국 내 결과만 우선적으로 보여주도록 설정할 수 있습니다.
#구글지도API #PlacesAPI #장소검색API #자동완성구현 #Autocomplete #웹개발 #GoogleMapsSDK #장소데이터 #API최적화 #지도라이브러리 #자바스크립트검색 #맛집지도만들기 #위치기반서비스개발
'API' 카테고리의 다른 글
| Google Maps API 구글 지도 API 연동 - 5. 커스터마이징 (0) | 2026.05.14 |
|---|---|
| Google Maps API 구글 지도 API 연동 - 4. 클러스터링 (0) | 2026.05.14 |
| Google Maps API 구글 지도 API 연동 - 2. 길찾기 (0) | 2026.05.14 |
| Google Maps API 구글 지도 API 연동 - 1. 시작하기 (0) | 2026.05.14 |
| 카카오 지도 연동하기 (0) | 2026.05.10 |
| 카카오 로그인 연동하기 (0) | 2026.05.10 |
- Total
- Today
- Yesterday
- 데이터베이스
- String
- DB연동
- 정보처리기사
- Android
- Class
- 자바
- C
- 자료구조
- Java
- C++ 클래스
- 벡터
- 배열
- 파이썬
- 알고리즘
- 상속
- 리스트
- 파일처리
- OpenCV
- C++
- 아두이노
- C언어
- 블루투스
- 문제풀이
- html
- 안드로이드
- 문자열
- MySQL
- 클래스
- c#
| 일 | 월 | 화 | 수 | 목 | 금 | 토 |
|---|---|---|---|---|---|---|
| 1 | 2 | |||||
| 3 | 4 | 5 | 6 | 7 | 8 | 9 |
| 10 | 11 | 12 | 13 | 14 | 15 | 16 |
| 17 | 18 | 19 | 20 | 21 | 22 | 23 |
| 24 | 25 | 26 | 27 | 28 | 29 | 30 |
| 31 |
