Flutter web 구글 directions API로 도착 소요 시간 가져오기
이제 FastAPI 서버 연동해봤으니 거기서 API key를 가져올 수 있는지 확인해야한다. (연동 방법은 이전 포스트 참조)
env 파일에서 key 잘 가져왔다! 이제 드디어 정말로 최적 경로와 시간을 가져와 볼것이다.
우선 main 부분부터 바꿔준다.
# main.py
from fastapi import FastAPI, Query
from fastapi.middleware.cors import CORSMiddleware
import requests
import os
from dotenv import load_dotenv
# .env 불러오기
load_dotenv()
app = FastAPI()
# CORS 설정 (Flutter Web용)
app.add_middleware(
CORSMiddleware,
allow_origins=["*"], # dev 용도, 배포 시엔 특정 origin만
allow_credentials=True,
allow_methods=["*"],
allow_headers=["*"],
)
GOOGLE_API_KEY = os.getenv("GOOGLE_API_KEY")
@app.get("/directions")
def get_directions(
origin: str = Query(...),
destination: str = Query(...),
mode: str = Query("driving")
):
url = "https://maps.googleapis.com/maps/api/directions/json"
params = {
"origin": origin,
"destination": destination,
"mode": mode,
"key": GOOGLE_API_KEY
}
response = requests.get(url, params=params)
return response.json()
저 "driving" 이란 값은 mode 설정하지 않으면 들어가는 기본 값으로, 호출할 때 mode = walking 이런식으로 넣어주면 도보 정보 또한 가져올 수 있다.
Flutter project로 넘어가서 uri 호출부 가본다.
기존에 불러오는 코드는 이런 식이라 CORS 정책에 걸리지만
final carUri = Uri.parse('https://maps.googleapis.com/maps/api/directions/json?...');
final res = await http.get(carUri); // CORS 보안문제로 걸린다.
아래와 같이 짜면 FastAPI 서버로 넘어간 후 그 서버에서 Directions API를 호출하게 된다.
// car
final carUri = Uri.parse('http://localhost:8000/directions').replace(queryParameters: {
'origin': '$startLat,$startLng',
'destination': '$endLat,$endLng',
'mode': 'driving',
});
final res = await http.get(carUri); // ✅ FastAPI가 대신 Google API 호출
// 도보
final walkUri = Uri.parse('http://localhost:8000/directions').replace(queryParameters: {
'origin': '$startLat,$startLng',
'destination': '$endLat,$endLng',
'mode': 'walking',
});
final walkRes = await http.get(walkUri);
- ModuleNotFoundError: No module named 'requests'
이런 오류가 떴는데 requests 모듈 없다는 뜻이다.
가상 환경에서 pip install requests 치고 모듈 다운받아주면 된다.
- 네트워크 오류 : RangeError(index) : Index out of range : no indices are valid: 0
final carDuration = (jsonDecode(carRes.body)['routes'][0]['legs'][0]['duration']['text']) ?? '정보 없음';
이 줄에서 'routes' 가 비어있어서 생기는 문제이다. 왜 비어있는지 원인 파악을 위해 debugPrint 한번 써보겠다.
if (carRes.statusCode == 200 && walkRes.statusCode == 200) {
debugPrint("길찾기 API 응답 성공");
final carData = json.decode(carRes.body);
final walkData = json.decode(walkRes.body);
debugPrint("자동차 응답 내용: ${jsonEncode(carData)}");
debugPrint("도보 응답 내용: ${jsonEncode(walkData)}");
길찾기 API 응답 성공
자동차 응답 내용:
{"available_travel_modes":["TRANSIT"],"geocoded_waypoints":[{},{}],"r
outes":[],"status":"ZERO_RESULTS"}
도보 응답 내용:
{"available_travel_modes":["TRANSIT"],"geocoded_waypoints":[{},{}],"r
outes":[],"status":"ZERO_RESULTS"}
ZERO_RESULTS가 뜬다. Google Directions API는 특정 구간이 너무 멀거나, 도보나 자동차 경로가 실제로 없다고 판단되면 빈 routes를 돌려준다고 한다(...) 실제 좌표가 이상한 건가 싶어서 들어가는 좌표 확인해보고 오겠다.
// 좌표 가져오기 (lat/lng 형태로 저장되어 있는 경우)
double? startLat = start['lat'] is double ? start['lat'] : double.tryParse(start['lat']?.toString() ?? '');
double? startLng = start['lng'] is double ? start['lng'] : double.tryParse(start['lng']?.toString() ?? '');
double? endLat = end['lat'] is double ? end['lat'] : double.tryParse(end['lat']?.toString() ?? '');
double? endLng = end['lng'] is double ? end['lng'] : double.tryParse(end['lng']?.toString() ?? '');
debugPrint("장소 좌표: ${start['name']}(${startLat}, ${startLng}) → ${end['name']}(${endLat}, ${endLng})");
시작 좌표와 끝 좌표에 debug 찍어줬다. 결과 확인해보면
🧭 좌표 디버깅: 씨네 인디 유(36.323769, 127.426711) → 왕관식당(36.315315, 127.437365) 🔍 길찾기 시작: 씨네 인디 유 → 왕관식당
잘나오는데??
Aㅏ... 그렇다.. 구글맵에 실제 경로 검색해보니 애초에 Google API는 대중교통 경로만 있고 자동차, 도보 경로는 존재 자체가 없던 것이었다...
대중교통 만이라도 가져와 보겠다...
final transitUri = Uri.parse('http://localhost:8000/directions').replace(queryParameters: {
'origin': '$startLat,$startLng',
'destination': '$endLat,$endLng',
'mode': 'transit',
});
debugPrint(" 대중교통 API 요청: ${transitUri.toString()}");
final transitRes = await http.get(transitUri); // FastAPI가 대신 Google API 호출
debugPrint("대중교통통 API 응답 코드: ${transitRes.statusCode}");
final carDuration = (jsonDecode(carRes.body)['routes'][0]['legs'][0]['duration']['text']) ?? '정보 없음';
final walkDuration = (jsonDecode(walkRes.body)['routes'][0]['legs'][0]['duration']['text']) ?? '정보 없음';
# 대중교통 Duration 추가
final transitDuration = (jsonDecode(transitRes.body)['routes'][0]['legs'][0]['duration']['text']) ?? '정보 없음';
times.add({
'car': carDuration,
'walk': walkDuration,
'transit': transitDuration,
});
# UI 부분
if (index < travelTimes.length)
Container(
margin: EdgeInsets.only(left: 16, bottom: 16),
padding: EdgeInsets.all(12),
decoration: BoxDecoration(
color: Colors.grey[100],
borderRadius: BorderRadius.circular(8),
),
child: Column(
children: [
Row(
children: [
Icon(Icons.directions_car, size: 16),
SizedBox(width: 4),
Text('자동차: ${travelTimes[index]['car']}'),
],
),
SizedBox(height: 4),
Row(
children: [
Icon(Icons.directions_walk, size: 16),
SizedBox(width: 4),
Text('도보: ${travelTimes[index]['walk']}'),
],
),
Row(
children: [
Icon(Icons.directions_transit, size: 16),
SizedBox(width: 4),
Text('대중교통: ${travelTimes[index]['transit']}'),
],
),
],
),
),
🚍 대중교통 응답 내용:
{"geocoded_waypoints":[{"geocoder_status":"OK","place_id":"ChIJa8pE0cVLZTURvRDhysQtuRM","types":["street_address"]},{"geocoder_status":"OK","place_id":"ChIJgab-LhhMZ
TURksQhCs0607M","types":["street_address"]}],"routes":[{"bounds":{"northeast":{"lat":36.36489,"lng":127.379312},"southwest":{"lat":36.31909,"lng":127.367342}},"copyrights":"Powered by Google, ©2025
잘들어온다... 하핳
젠장 잘 들어온다 결과가 잘나오는데 앞서 댕고생 했던 시간을 생각하니 짜증이 치솟는다

... 아무래도 자동차, 도보 정보까지 가져오려면 naver directions API와 혼합해서 사용해야 할 필요가 있겠다. 다음 포스트에서 진행하겠다,,