SK open API
SK텔레콤 데이터와 시각화 가공을 지원 받을 수 있는, 데이터 바우처 사업 에 대해서 알아보세요! 더 알아보기
openapi.sk.com
여기에서 회원가입 후 대시보드로 들어가준다.
(이미 만들어 놓았다) 이다음 앱 오른쪽 화살표 눌러서
앱 만들기에서 이름만 입력해주면 된다.
Tmap을 사용하려면 따로 신청을 해줘야 하는데 홈화면에 > Products > TMAP > API 사용 요금 창으로 가서 원하는 요금제 사용하기 버튼 누르면 된다. 필자는 Free로 했고, 자동 신청 등록되어 바로 쓸 수 있다.
왼쪽 짝대기 세개 누르면 나온다.
먼저 env 파일에 앱키를 넣어준다음 fastAPI 서버 호출부에 tmap 호출을 추가한다.
# 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=["*"], # 개발 단계에서만 *, 배포 시엔 도메인 지정 권장
allow_credentials=True,
allow_methods=["*"],
allow_headers=["*"],
)
# 환경변수에서 API 키 불러오기
GOOGLE_API_KEY = os.getenv("GOOGLE_API_KEY")
TMAP_API_KEY = os.getenv("TMAP_API_KEY")
# 기본 루트 엔드포인트
@app.get("/")
def read_root():
return {
"message": "FastAPI is running!",
"google_key_loaded": bool(GOOGLE_API_KEY),
"tmap_key_loaded": bool(TMAP_API_KEY),
}
# ✅ Google Directions API (driving / walking / transit)
@app.get("/directions")
def get_directions(
origin: str = Query(...),
destination: str = Query(...),
mode: str = Query("driving") # driving, walking, transit
):
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()
# ✅ Tmap 도보 경로 요청 API
@app.get("/tmap/walk")
def get_tmap_walk_time(
start_lat: float = Query(...),
start_lng: float = Query(...),
end_lat: float = Query(...),
end_lng: float = Query(...)
):
url = "https://apis.openapi.sk.com/tmap/routes/pedestrian"
headers = {
"Content-Type": "application/json",
"appKey": TMAP_API_KEY
}
body = {
"startX": str(start_lng),
"startY": str(start_lat),
"endX": str(end_lng),
"endY": str(end_lat),
"reqCoordType": "WGS84GEO",
"resCoordType": "WGS84GEO"
}
response = requests.post(url, json=body, headers=headers)
return response.json()
서버 정상 작동 확인 되었고, 이제 flutter 단에서 정보를 불러와보겠다.
❌ 도보 API 응답 실패: {"detail":[{"type":"missing","loc":["query","start_lat"],"msg":"Field required","input":null},{"type":"missing","loc":["query","start_lng"] ,"msg":"Field required","input":null},{"type":"missing","loc":["query","end_lat"]," msg":"Field required","input":null},{"type":"missing","loc":["query","end_lng"]," msg":"Field required","input":null}]}
이런 오류가 떴는데
자세히 보면 서버는 start_lat 이런식으로 _ 를 쓴 이름인 반면에 flutter 쪽에서는 startLat 이런 이름이다. 이름이 다르면 못가져오는게 당연하겠지 (...) 다시 서버 이름으로 제대로 맞춰주면
✅ 도보 API 응답 성공 (Tmap)
Tmap 도보 응답 원본:
{"error":{"id":"400","category":"tmap","code":"9401","message":"íì
íë¼ë©í°ê° ììµëë¤."}}
🚶 Tmap 응답에 features 없음
// 안됐다...
할 수 없이 sk open API 사이트에서 오류코드를 찾아보니 필수 파라미터가 없다고 한다.
계속 안돼서 서버에 직접 파라미터 넣어서 요청해봤다.
http://localhost:8000/tmap/walk?startX=127.384547&startY=36.351411&endX=127.388727&endY=36.350528
{"error":{"id":"400","category":"tmap","code":"9401","message":"필수 파라메터가 없습니다."}}
역시 안된다. 파라미터 내용이 뭔가 잘못된건 확실한 듯 하다.
Guide | T MAP API
tmapapi.tmapmobility.com
여기에서 보행자 경로 안내를 들어가면 원하는 레퍼런스로 넘어갈 수 있다.
1) Query Params
- version (required)
2) Body Params
- startX (required)
- startY (required)
- endX (required)
- endY (required)
- startName (required)
- endName (required)
여기까지가 필수 사항들이다. 여기가 문제였구만! 이제까지는 startX, startY endX, endY만 계속 가져왔다...
게다가 Tmap api는 POST + JSON body 방식이라 기존에 사용했던 GET 방식 + 쿼리파라미터 방식이랑 차이가 있었기 때문에 mismatch가 생긴것이였다.
그 후에도 version 파라미터 때문에 생긴 네트워크 오류 client Exception도 있었는데 이건 클라이언트 단에서 버전을 가져오기 보단 서버에 애초부터 version 정보를 넣고 호출하게 해서 해결했다.
최종 실행결과 + 코드이다.
# main.py
from fastapi import FastAPI, Query, Body
from fastapi.middleware.cors import CORSMiddleware
from pydantic import BaseModel
import requests
import os
from dotenv import load_dotenv
# .env 불러오기
load_dotenv()
app = FastAPI()
# CORS 설정 (Flutter Web용)
app.add_middleware(
CORSMiddleware,
allow_origins=["*"], # 개발 단계에서만 *, 배포 시엔 도메인 지정 권장
allow_credentials=True,
allow_methods=["*"],
allow_headers=["*"],
)
# 환경변수에서 API 키 불러오기
GOOGLE_API_KEY = os.getenv("GOOGLE_API_KEY")
TMAP_API_KEY = os.getenv("TMAP_API_KEY")
# 기본 루트 엔드포인트
@app.get("/")
def read_root():
return {
"message": "FastAPI is running!",
"google_key_loaded": bool(GOOGLE_API_KEY),
"tmap_key_loaded": bool(TMAP_API_KEY),
}
# ✅ Google Directions API (driving / walking / transit)
@app.get("/directions")
def get_directions(
origin: str = Query(...),
destination: str = Query(...),
mode: str = Query("driving") # driving, walking, transit
):
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()
class WalkRequest(BaseModel):
startX: float
startY: float
endX: float
endY: float
startName: str
endName: str
# ✅ Tmap 도보 경로 요청 API
@app.post("/tmap/walk")
def get_tmap_walk(body: WalkRequest = Body(...)):
url = f"https://apis.openapi.sk.com/tmap/routes/pedestrian?version=1"
headers = {
"appKey": TMAP_API_KEY,
"Content-Type": "application/json",
}
payload = {
"startX": body.startX,
"startY": body.startY,
"endX": body.endX,
"endY": body.endY,
"startName": body.startName,
"endName": body.endName,
"reqCoordType": "WGS84GEO",
"resCoordType": "WGS84GEO"
}
response = requests.post(url, headers=headers, json=payload)
return response.json()
# travel_course_detail_screen.dart
try {
// 자동차 시간 - URL을 명확하게 구성하고 인코딩
final carUri = Uri.parse('http://localhost:8000/directions').replace(queryParameters: {
'origin': '$startLat,$startLng',
'destination': '$endLat,$endLng',
'mode': 'driving',
});
debugPrint("🚗 자동차 API 요청: ${carUri.toString()}");
final carRes = await http.get(carUri); // FastAPI가 대신 Google API 호출
debugPrint("자동차 API 응답 코드: ${carRes.statusCode}");
// 도보 시간 - URL을 명확하게 구성하고 인코딩
final walkUri = Uri.parse('http://localhost:8000/tmap/walk');
debugPrint("🚶 도보 API 요청: ${walkUri.toString()}");
final walkRes = await http.post(
walkUri,
headers: {'Content-Type': 'application/json'},
body: jsonEncode({
'startX': '$startLng',
'startY': '$startLat',
'endX': '$endLng',
'endY': '$endLat',
'startName': start['name'] ?? '출발지',
'endName': end['name'] ?? '도착지',
}),
);
debugPrint("도보 API 응답 코드: ${walkRes.statusCode}");
// 대중교통 시간 - URL을 명확하게 구성하고 인코딩
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}");
String carDuration = '정보 없음';
String walkDuration = '정보 없음';
String transitDuration = '정보 없음';
// 자동차 응답 처리
if (carRes.statusCode == 200) {
debugPrint("✅ 길찾기 API 응답 성공");
final carData = json.decode(carRes.body);
if(carData['routes'] != null && carData['routes'].isNotEmpty) {
carDuration = carData['routes'][0]['legs'][0]['duration']['text'] ?? '정보 없음';
} else {
debugPrint("🚗 ZERO_RESULTS or route 없음");
}
} else {
debugPrint("❌ 자동차 API 응답 실패: ${carRes.body}");
}
// 도보 응답 처리 (tmap)
if (walkRes.statusCode == 200) {
debugPrint("✅ 도보 API 응답 성공 (Tmap)");
final walkData = json.decode(walkRes.body);
//debugPrint("Tmap 도보 응답 원본: ${walkRes.body}");
if (walkData['features'] != null && walkData['features'].isNotEmpty) {
final properties = walkData['features'][0]['properties'];
final totalTime = properties['totalTime']; // 단위: 초 (seconds)
if (totalTime != null) {
walkDuration = '${(totalTime / 60).round()}분'; // 분 단위로 표시
} else {
debugPrint("⚠️ totalTime 없음");
}
} else {
debugPrint("🚶 Tmap 응답에 features 없음");
}
} else {
debugPrint("❌ 도보 API 응답 실패: ${walkRes.body}");
}
// 대중교통 응답 처리
if (transitRes.statusCode == 200) {
debugPrint("✅ 대중교통 API 응답 성공");
final transitData = json.decode(transitRes.body);
if(transitData['routes'] != null && transitData['routes'].isNotEmpty) {
transitDuration = transitData['routes'][0]['legs'][0]['duration']['text'] ?? '정보 없음';
} else {
debugPrint("🚍 ZERO_RESULTS or route 없음");
}
times.add({
'car': carDuration,
'walk': walkDuration,
'transit': transitDuration,
});
debugPrint("🕒 계산된 시간: 자동차 $carDuration분, 도보 $walkDuration분, 대중교통 $transitDuration분");
} else {
debugPrint("❌ 길찾기 API 응답 실패: 자동차(${carRes.statusCode}), 도보(${walkRes.statusCode}), 대중교통 (${transitRes.statusCode})");
// 에러 응답 내용 확인
if (carRes.statusCode != 200) {
debugPrint("자동차 API 에러: ${carRes.body}");
}
if (walkRes.statusCode != 200) {
debugPrint("도보 API 에러: ${walkRes.body}");
}
if (transitRes.statusCode != 200) {
debugPrint("대중교통 API 에러: ${transitRes.body}");
}
times.add({
'car': '통신 오류',
'walk': '통신 오류',
'transit': '통신 오류',
});
}
} catch (e) {
debugPrint("❌ 길찾기 API 호출 오류: $e");
times.add({
'car': '에러',
'walk': '에러',
'transit': '에러',
});
setState(() {
apiError = '네트워크 오류: $e';
});
}
}
'프로젝트 (Travel Maker)' 카테고리의 다른 글
Flutter 프로젝트 페이징 처리 (0) | 2025.04.29 |
---|---|
TmapAPI로 도착지<->목적지 소요 시간 가져오기 (자동차) (2) | 2025.04.29 |
Flutter web 구글 directions API로 도착 소요 시간 가져오기 (2) | 2025.04.24 |
FastAPI 시작하고 프로젝트에 연동하기 (0) | 2025.04.23 |
Flutter dotenv 패키지 관련 오류 (0) | 2025.04.22 |