많은 지도 서비스에서 '다시 검색' 버튼을 통해 새로운 위치에서 재조회를 서비스를 제공합니다.


반대로 야놀자의 경우는 드래그를 통해 이동하는 위치에 따라서 검색이 자동으로 발생하는 것처럼 보입니다.
누구나 가고 싶어하는 이 회사들에서 서비스에 알맞은 방법을 택했을 겁니다.
하지만 이 글은 3티어 구조(웹 서버 - API 서버 - RDB)에서 지도 드래그 시 원활한 매장 재조회가 이뤄지도록 서비스를 개선한 경험을 작성한 것이며, 따라서 새로운 인프라 자원을 학습해서 서비스에 적용하기에는 시간이 부족하거나 재정적 이슈로 사용할 수 없는 환경에 계신 분에게 도움이 됐으면 합니다.
웹은 Vue, Naver maps API v3 그리고 DB는 MySQL 사용했으며 DB에 저장된 매장 데이터에는 위도와 경도를 포함하고 있습니다.
: AS-IS
- 설정
- 지도 zoom_level 기본 값 16, 축척 100m
- 매장 조회 api 호출 시점
- zoom_level에 따라 축척의 3배 거리를 이동했다고 판단될 때, 자동 재조회
- 재조회 판단 방법
- js 문법으로 두 좌표 사의 거리를 계산해서 재조회 여부 판단
- 매장 조회 api의 매장 조회 반경
- 반지름 r = zoom_level에 따라 축척의 3배
- 문제점
- 생각보다 조금만 움직여도 재조회가 발생
- 화면의 중심에서 벗어난 매장의 마커가 사라지는 경우가 있는데, 재조회 임계치가 생각보다 작다보니 중심에서 벗어난 건지 재조회가 발생해서 사라진 건지 구분 불가
: TO-BE
- 개선점
- 매장 조회 api의 매장 조회 반경 수정
- 기본 zoom_level 16 ~ 최대 zoom_level까지 고정 반경 설정 (r = 900m)
- 매장 조회 api 호출 시점 수정
- 고정된 경계 임계치 1.2km를 벗어났을 때, 자동 재조회
- 재조회 판단 방법 수정
- naver maps api를 활용해서 위경도 경계 내에 드래그된 좌표 포함 여부로 재조회 판단
- 매장 조회 api의 매장 조회 반경 수정
: naver.maps.LatLngBounds
const ne = startCoord.destinationPoint(45, 1200);
const sw = startCoord.destinationPoint(225, 1200);
const neCoord = window.naver.maps.LatLng(ne._lat, ne._lng);
const swCoord = window.naver.maps.LatLng(sw._lat, sw._lng);
const coordBound1 = window.naver.maps.LatLngBounds(neCoord, swCoord);
console.log('coordBound1: ', coordBound1.toString());
const e = startCoord.destinationPoint(90, 1200);
const w = startCoord.destinationPoint(270, 1200);
const eCoord = window.naver.maps.LatLng(e._lat, e._lng);
const wCoord = window.naver.maps.LatLng(w._lat, w._lng);
const coordBound2 = window.naver.maps.LatLngBounds(eCoord, wCoord);
console.log('coordBound2: ', coordBound2.toString());
const n = startCoord.destinationPoint(0, 1200);
const s = startCoord.destinationPoint(180, 1200);
const nCoord = window.naver.maps.LatLng(n._lat, n._lng);
const sCoord = window.naver.maps.LatLng(s._lat, s._lng);
const coordBound3 = window.naver.maps.LatLngBounds(nCoord, sCoord);
console.log('coordBound3: ', coordBound3.toString());
const nw = startCoord.destinationPoint(-45, 1200);
const se = startCoord.destinationPoint(-225, 1200);
const nwCoord = window.naver.maps.LatLng(nw._lat, nw._lng);
const seCoord = window.naver.maps.LatLng(se._lat, se._lng);
const coordBound4 = window.naver.maps.LatLngBounds(nwCoord, seCoord);
console.log('coordBound4: ', coordBound4.toString());
- Returns는 필드는 4개이지만, (max, min)과 (ne, sw) 필드가 중복된 값이으로 실제로는 2개의 값을 리턴한다.

- 1, 4번 경계가 같은 값을 리턴해서 겹친 선을 확인할 수 있다.
- 따라서, 1~3번 경계를 통해 새로운 좌표를 검증하면 된다.
const startCoord = window.naver.maps.LatLng(최초중심.lat, 최초중심.lng);
// console.log('startCoord: ', startCoord.toString());
const endCoord = window.naver.maps.LatLng(이동된중심.lat, 이동된중심.lng);
// console.log('endCoord: ', endCoord.toString());
// 중심으로부터 특정 각도, 특정 거리만큼 떨어진 위치의 위경도
// 경계 바운더리는 사각형이므로 최대한 원과 동일하게 유지하기 위해 두 경계 바운더리를 구성하여 별 형태의 경계 구성
const ne = startCoord.destinationPoint(45, 1200);
const sw = startCoord.destinationPoint(225, 1200);
const neCoord = window.naver.maps.LatLng(ne._lat, ne._lng);
const swCoord = window.naver.maps.LatLng(sw._lat, sw._lng);
const coordBound1 = window.naver.maps.LatLngBounds(neCoord, swCoord);
// console.log('coordBound1: ', coordBound1.toString());
const validateRefresh1 = coordBound1.hasLatLng(endCoord);
console.log('validateRefresh1: ', validateRefresh1);
const e = startCoord.destinationPoint(90, 1200);
const w = startCoord.destinationPoint(270, 1200);
const eCoord = window.naver.maps.LatLng(e._lat, e._lng);
const wCoord = window.naver.maps.LatLng(w._lat, w._lng);
const coordBound2 = window.naver.maps.LatLngBounds(eCoord, wCoord);
// console.log('coordBound2: ', coordBound2.toString());
const validateRefresh2 = coordBound2.hasLatLng(endCoord);
console.log('validateRefresh2: ', validateRefresh2);
const n = startCoord.destinationPoint(0, 1200);
const s = startCoord.destinationPoint(180, 1200);
const nCoord = window.naver.maps.LatLng(n._lat, n._lng);
const sCoord = window.naver.maps.LatLng(s._lat, s._lng);
const coordBound3 = window.naver.maps.LatLngBounds(nCoord, sCoord);
// console.log('coordBound3: ', coordBound3.toString());
const validateRefresh3 = coordBound3.hasLatLng(endCoord);
console.log('validateRefresh3: ', validateRefresh3);
// 세 경계 간 겹치는 부분이 있는지 여부
const test1 = coordBound1.intersects(coordBound2);
console.log("겹치는 부분이 있는지 여부1: ", test1); // true
const test2 = coordBound1.intersects(coordBound3);
console.log("겹치는 부분이 있는지 여부2: ", test2); // true
const test3 = coordBound2.intersects(coordBound3);
console.log("겹치는 부분이 있는지 여부3: ", test3); // true
// 별 형태의 경계에 이동된중심 위경도가 안 될 때,
if (!validateRefresh1 && !validateRefresh2 && !validateRefresh3) {
// 1. 최초중심 변경
// 2. 조회 API 호출

- 사각형의 중심에서 꼭지점까지의 거리를 r (1.2km)라 했을 때, 저 두 개의 경계를 벗어나면 자동으로 API 조회를 해서 화면을 다시 그립니다.
: 결론
- 생각과 다른 부분
- 예상한 범위 내의 이동중심으로 이동했을 때, 1번 경계(대각선)가 아닌 2, 3번 경계에 대해서는 .hasLatLng() 리턴 값이 false로 출력
- 노란색 범위 내인 것 같지만 validateRefresh1=true, validateRefresh2=false, validateRefresh3=false 출력
- 예상한 범위 내의 이동중심으로 이동했을 때, 1번 경계(대각선)가 아닌 2, 3번 경계에 대해서는 .hasLatLng() 리턴 값이 false로 출력
- 결론
- toString()으로 경계 좌표로 출력된 값을 지도에 찍어보면 예상된 위치에 좌표가 나타난다.
- LatLngBounds()가 사각 박스를 내가 생각한 대로 그리지 않는 것 같다.
- 그래도 3개의 경계에 대한 조건을 사용해도 예상 밖의 결과(조회가 이상한 때에 된다)가 나타나지는 않았다.
- 애매하다고 느낀다면 1번 경계(대각선) 조건만 사용하면 될 것 같다.
: 참고 문서
naver maps api: https://navermaps.github.io/maps.js.ncp/
vue3-naver-maps: https://dongkyuuuu.github.io/vue3-naver-maps/
'개발 이야기' 카테고리의 다른 글
| 플러터를 학습하고 저렴하고 빠르게 앱을 배포해보자 (2) | 2025.08.05 |
|---|---|
| AWS를 학습하고 서비스를 빠르게 배포해보자 (3) | 2025.08.05 |
| 후불 결제 솔루션의 미수금 결제 배치 서비스 개선 (0) | 2024.08.21 |