반경 설정하는 부분이 이상해서 잘 고친 부분 수정한 뒤 장소 필터링 기능 코드를 다시 올린다.
사용자가 UI에서 여행반경을 설정하면 FireStore에 있는 places/해당장소 문서/lng, lat 정보를 가져와서 장소를 필터링 해준다.
import 'dart:math';
// 좌표 간 거리 계산 함수 (단위: km)
double calculateDistance(double lat1, double lon1, double lat2, double lon2) {
const double earthRadius = 6371.0;
final dLat = _degToRad(lat2 - lat1);
final dLon = _degToRad(lon2 - lon1);
final a = sin(dLat / 2) * sin(dLat / 2) +
cos(_degToRad(lat1)) * cos(_degToRad(lat2)) *
sin(dLon / 2) * sin(dLon / 2);
final c = 2 * atan2(sqrt(a), sqrt(1 - a));
return earthRadius * c;
}
double _degToRad(double degree) => degree * pi / 180.0;
좌표 간 거리 계산 함수를 만들었다. 함수를 사용할 클래스 안에 넣어준다.
Future<void> fetchPlacesAndAskGpt() async {
final url = Uri.parse(
'https://firestore.googleapis.com/v1/projects/$firebaseProjectId/databases/(default)/documents/places?key=$firebaseApiKey',
);
final response = await http.get(url);
if (response.statusCode != 200) {
setState(() {
gptAnswer = '❌ Firestore 불러오기 실패';
isLoading = false;
});
return;
}
final jsonData = jsonDecode(utf8.decode(response.bodyBytes));
final documents = jsonData['documents'] ?? [];
final List<Map<String, dynamic>> placeList = [];
final centerLat = location['lat'] ?? 0.0;
final centerLng = location['lng'] ?? 0.0;
final radiusKm = location['radius_km']?.toDouble() ?? 10.0;
debugPrint('반경: $radiusKm km, 위치: ($centerLat, $centerLng)');
debugPrint('실제 반경: $radiusKm km');
for (final doc in documents) {
final fields = doc['fields'];
final name = fields['pname']?['stringValue'] ?? '이름 없음';
final lat = double.tryParse(fields['location']?['mapValue']?['fields']?['lat']?['doubleValue']?.toString() ?? '') ?? 0.0;
final lng = double.tryParse(fields['location']?['mapValue']?['fields']?['lng']?['doubleValue']?.toString() ?? '') ?? 0.0;
final distance = calculateDistance(centerLat, centerLng, lat, lng);
print('📍 $name → 거리: \${distance.toStringAsFixed(2)} km');
if (distance <= radiusKm) {
print('✅ 포함됨');
final ptags = fields['ptags']?['arrayValue']?['values'];
final tagList = ptags is List ? ptags.map((e) => e['stringValue'] as String).toList() : [];
final address = fields['address']?['stringValue'] ?? '주소 없음';
placeList.add({
'name': name,
'address': address,
'tags': tagList,
'lat': lat,
'lng': lng,
});
} else {
print('❌ 제외됨');
}
}
// 더 엄격한 프롬프트 작성
final prompt = StringBuffer()
..writeln("당신은 여행 플래너입니다. 아래 조건에 따라 하나의 종합적인 '${totalDays}일 일정' 코스를 JSON 배열로 2~3개 추천해주세요.")
..writeln("출력 형식은 반드시 다음과 같아야 합니다:")
..writeln("[")
..writeln(" { courseName: string, scores: {}, comparisons: [], places: [ { name, address, day, duration } ] }, ...")
..writeln("]")
..writeln("조건:")
..writeln("- 동행자 유형: ${companions.join(', ')}")
..writeln("- 활동: ${activities.join(', ')}")
..writeln("- 테마: ${themes.join(', ')}")
..writeln("- 장소 키워드: ${places.join(', ')}")
..writeln("- 여행 기간: ${startDate.year}.${startDate.month}.${startDate.day} ~ ${endDate.year}.${endDate.month}.${endDate.day} (${totalDays}일)")
..writeln()
..writeln("⚠️ 주의사항:")
..writeln("- 각 코스는 1일차부터 ${totalDays}일차까지 모든 날을 포함해야 하며, `day` 필드를 통해 명시해주세요.")
..writeln("- 장소 간 동선을 고려하여 가능한 한 효율적인 이동 경로로 배치해 주세요.")
..writeln("- 각 장소에 머무는 시간은 `duration` 필드로 1~4시간 사이로 적절히 조절해주세요.")
..writeln()
..writeln("📍 후보 장소 목록:");
for (var p in placeList) {
prompt.writeln("- ${p['name']} / ${p['address']} / ${p['tags'].join(', ')}");
}
for (var p in placeList) {
prompt.writeln("- ${p['name']} / ${p['address']} / ${p['tags'].join(', ')}");
}
final result = await askGPT(prompt.toString());
final jsonStr = extractJson(result);
try {
final parsed = jsonDecode(jsonStr);
if (parsed is List) {
final processedResults = validateAndFixDays(parsed);
setState(() {
travelResults = processedResults;
isLoading = false;
gptAnswer = '여행 코스가 준비되었습니다!';
});
} else {
throw Exception("리스트 형태 아님");
}
} catch (e) {
print("JSON 파싱 오류: $e\n응답 내용: $result");
setState(() {
travelResults = [];
gptAnswer = '❌ 응답 형식 오류';
isLoading = false;
});
}
}
double.tryParse는 타입 에러 방지를 위해 사용한다.
final lat, lng 부분이 firestore에서 가져오는 장소의 위치 정보이다.
"location": {
"lat": 36.35,
"lng": 127.38
}
FireStore에 저장한 방식이다. 실제 저장은 places(엔드포인트)/장소 문서/location/lat, lng 이다.
코드 알고리즘은 맞아보이는데 범위 지정이 안되고 전체장소만 계속 가져왔다. 왜 이러는지는 flutter map 공부 후 후술해보겠다.
-> 해결 완료 했다. 반경 설정 부분에서 오류가 난 것이었다. 실행 부분은 반경 설정 포스트에 사진 올려놨다.
'프로젝트' 카테고리의 다른 글
Flutter Map에 장소 마커, 경로 표시 (1) | 2025.04.22 |
---|---|
Flutter Map으로 여행 반경 설정 (2) | 2025.04.17 |
Firebase users 정보 UI로 가져오기 (0) | 2025.04.15 |
TourAPI 대전 전체 장소 FB에 저장하는 코드 (0) | 2025.04.14 |
TourAPI 매핑 작업 - Firestore에 분류 태그 저장하기 (11) | 2025.04.11 |