import 'dart:convert'; import 'package:http/http.dart' as http; import 'package:lunchpick/core/utils/app_logger.dart'; /// 주소를 위도/경도로 변환하는 간단한 지오코딩(Geocoding) 서비스 class GeocodingService { static const _endpoint = 'https://nominatim.openstreetmap.org/search'; static const _fallbackLatitude = 37.5665; // 서울시청 위도 static const _fallbackLongitude = 126.9780; // 서울시청 경도 /// 도로명/지번 주소를 기반으로 위경도를 조회한다. /// /// 무료(Nominatim) 엔드포인트를 사용하며 별도 API 키가 필요 없다. /// 실패 시 null을 반환하고, 호출 측에서 기본 좌표를 사용할 수 있게 둔다. Future<({double latitude, double longitude})?> geocode(String address) async { if (address.trim().isEmpty) return null; try { final uri = Uri.parse( '$_endpoint?format=json&limit=1&q=${Uri.encodeQueryComponent(address)}', ); // Nominatim은 User-Agent 헤더를 요구한다. final response = await http.get( uri, headers: const {'User-Agent': 'lunchpick-geocoder/1.0'}, ); if (response.statusCode != 200) { AppLogger.debug('[GeocodingService] 실패 status: ${response.statusCode}'); return null; } final List results = jsonDecode(response.body) as List; if (results.isEmpty) return null; final first = results.first as Map; final lat = double.tryParse(first['lat']?.toString() ?? ''); final lon = double.tryParse(first['lon']?.toString() ?? ''); if (lat == null || lon == null) { AppLogger.debug('[GeocodingService] 응답 파싱 실패: ${first.toString()}'); return null; } return (latitude: lat, longitude: lon); } catch (e) { AppLogger.debug('[GeocodingService] 예외 발생: $e'); return null; } } /// 기본 좌표(서울시청)를 반환한다. ({double latitude, double longitude}) defaultCoordinates() { return (latitude: _fallbackLatitude, longitude: _fallbackLongitude); } }