From c607a52962a9e043b15c9329d602bd2f3c868663 Mon Sep 17 00:00:00 2001 From: JiWoong Sul Date: Wed, 28 Jan 2026 18:54:37 +0900 Subject: [PATCH] =?UTF-8?q?fix(geocoding):=20=EC=A3=BC=EC=86=8C=EC=97=90?= =?UTF-8?q?=EC=84=9C=20=EC=B8=B5=EC=88=98/=EC=83=81=ED=98=B8=EB=AA=85=20?= =?UTF-8?q?=EC=A0=9C=EA=B1=B0=20=EB=A1=9C=EC=A7=81=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - _cleanAddress() 메서드 추가 - 건물번호 뒤 층수 정보 제거 (예: "6-4 1층" → "6-4") - 건물번호 뒤 상호명 제거 (예: "6-4 이자카야 혼네" → "6-4") - geocode() 호출 전 주소 정리 적용 --- lib/core/services/geocoding_service.dart | 42 ++++++++++++++++++++++-- 1 file changed, 40 insertions(+), 2 deletions(-) diff --git a/lib/core/services/geocoding_service.dart b/lib/core/services/geocoding_service.dart index ba7d8b3..f03abed 100644 --- a/lib/core/services/geocoding_service.dart +++ b/lib/core/services/geocoding_service.dart @@ -17,8 +17,11 @@ class GeocodingService { Future<({double latitude, double longitude})?> geocode(String address) async { if (address.trim().isEmpty) return null; + // 주소 전처리: 상세 주소(층수, 상호명 등) 제거 + final cleanedAddress = _cleanAddress(address); + // 1차: VWorld 지오코딩 시도 (키가 존재할 때만) - final vworldResult = await _geocodeWithVworld(address); + final vworldResult = await _geocodeWithVworld(cleanedAddress); if (vworldResult != null) { return vworldResult; } @@ -26,7 +29,7 @@ class GeocodingService { // 2차: Nominatim (fallback) try { final uri = Uri.parse( - '$_endpoint?format=json&limit=1&q=${Uri.encodeQueryComponent(address)}', + '$_endpoint?format=json&limit=1&q=${Uri.encodeQueryComponent(cleanedAddress)}', ); // Nominatim은 User-Agent 헤더를 요구한다. @@ -121,4 +124,39 @@ class GeocodingService { return null; } } + + /// 주소에서 상세 주소(층수, 상호명 등)를 제거하여 순수 도로명 주소만 추출한다. + /// + /// 예시: + /// - "서울 관악구 관악로14길 6-4 1층 이자카야 혼네" → "서울 관악구 관악로14길 6-4" + /// - "서울특별시 강남구 테헤란로 123 B1 스타벅스" → "서울특별시 강남구 테헤란로 123" + String _cleanAddress(String address) { + final trimmed = address.trim(); + + // 패턴 1: 건물번호 뒤에 층수 정보가 있는 경우 (1층, B1, 지하1층 등) + // 도로명 주소의 건물번호는 숫자 또는 숫자-숫자 형태 + final floorPattern = RegExp( + r'(\d+(?:-\d+)?)\s+(?:\d+층|[Bb]\d+|지하\d*층?).*$', + ); + final floorMatch = floorPattern.firstMatch(trimmed); + if (floorMatch != null) { + final buildingNumber = floorMatch.group(1); + final beforeMatch = trimmed.substring(0, floorMatch.start); + return '$beforeMatch$buildingNumber'.trim(); + } + + // 패턴 2: 건물번호 뒤에 상호명이 바로 오는 경우 (공백 + 한글/영문) + // 단, 구/동/로/길 같은 주소 구성요소는 제외 + final namePattern = RegExp( + r'(\d+(?:-\d+)?)\s+(?![가-힣]+[구동로길읍면리]\s)([가-힣a-zA-Z&]+.*)$', + ); + final nameMatch = namePattern.firstMatch(trimmed); + if (nameMatch != null) { + final buildingNumber = nameMatch.group(1); + final beforeMatch = trimmed.substring(0, nameMatch.start); + return '$beforeMatch$buildingNumber'.trim(); + } + + return trimmed; + } }