feat(app): add vworld geocoding and native ads placeholders
This commit is contained in:
@@ -1,6 +1,7 @@
|
||||
import 'dart:convert';
|
||||
|
||||
import 'package:http/http.dart' as http;
|
||||
import 'package:lunchpick/core/constants/api_keys.dart';
|
||||
import 'package:lunchpick/core/utils/app_logger.dart';
|
||||
|
||||
/// 주소를 위도/경도로 변환하는 간단한 지오코딩(Geocoding) 서비스
|
||||
@@ -16,6 +17,13 @@ class GeocodingService {
|
||||
Future<({double latitude, double longitude})?> geocode(String address) async {
|
||||
if (address.trim().isEmpty) return null;
|
||||
|
||||
// 1차: VWorld 지오코딩 시도 (키가 존재할 때만)
|
||||
final vworldResult = await _geocodeWithVworld(address);
|
||||
if (vworldResult != null) {
|
||||
return vworldResult;
|
||||
}
|
||||
|
||||
// 2차: Nominatim (fallback)
|
||||
try {
|
||||
final uri = Uri.parse(
|
||||
'$_endpoint?format=json&limit=1&q=${Uri.encodeQueryComponent(address)}',
|
||||
@@ -55,4 +63,62 @@ class GeocodingService {
|
||||
({double latitude, double longitude}) defaultCoordinates() {
|
||||
return (latitude: _fallbackLatitude, longitude: _fallbackLongitude);
|
||||
}
|
||||
|
||||
Future<({double latitude, double longitude})?> _geocodeWithVworld(
|
||||
String address,
|
||||
) async {
|
||||
final apiKey = ApiKeys.vworldApiKey;
|
||||
if (apiKey.isEmpty) {
|
||||
return null;
|
||||
}
|
||||
|
||||
try {
|
||||
final uri = Uri.https('api.vworld.kr', '/req/address', {
|
||||
'service': 'address',
|
||||
'request': 'getcoord',
|
||||
'format': 'json',
|
||||
'type': 'road', // 도로명 주소 기준
|
||||
'key': apiKey,
|
||||
'address': address,
|
||||
});
|
||||
|
||||
final response = await http.get(
|
||||
uri,
|
||||
headers: const {'User-Agent': 'lunchpick-geocoder/1.0'},
|
||||
);
|
||||
|
||||
if (response.statusCode != 200) {
|
||||
AppLogger.debug(
|
||||
'[GeocodingService] VWorld 실패 status: ${response.statusCode}',
|
||||
);
|
||||
return null;
|
||||
}
|
||||
|
||||
final Map<String, dynamic> json = jsonDecode(response.body);
|
||||
final responseNode = json['response'] as Map<String, dynamic>?;
|
||||
if (responseNode == null || responseNode['status'] != 'OK') {
|
||||
AppLogger.debug('[GeocodingService] VWorld 응답 오류: ${response.body}');
|
||||
return null;
|
||||
}
|
||||
|
||||
// VWorld 포인트는 WGS84 lon/lat 순서(x=lon, y=lat)
|
||||
final result = responseNode['result'] as Map<String, dynamic>?;
|
||||
final point = result?['point'] as Map<String, dynamic>?;
|
||||
final x = point?['x']?.toString();
|
||||
final y = point?['y']?.toString();
|
||||
final lon = x != null ? double.tryParse(x) : null;
|
||||
final lat = y != null ? double.tryParse(y) : null;
|
||||
if (lat == null || lon == null) {
|
||||
AppLogger.debug(
|
||||
'[GeocodingService] VWorld 좌표 파싱 실패: ${point.toString()}',
|
||||
);
|
||||
return null;
|
||||
}
|
||||
|
||||
return (latitude: lat, longitude: lon);
|
||||
} catch (e) {
|
||||
AppLogger.debug('[GeocodingService] VWorld 예외: $e');
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user