API

날씨 API 가져오기

seongmin08 2025. 11. 19. 22:27

💡 들어가며

우리는 사용자의 현재 위치를 받아서 외부의 **날씨 API(OpenWeather 등)**에 전달한 뒤,
반환된 온도·기상 상태·예보 정보 등을 화면에 띄울 수 있다.

오늘은 날씨 API 연동을 통해 각 날씨 정보를 가져와 앱 내 띄워 보도록 한다.


🌍 1. 날씨 API란?

앱이나 웹에서 현재 날씨를 표시하려면 기상 정보를 얻어야 한다.
이때 대부분은 기상청 API 또는 글로벌 날씨 API를 사용해야 하는데

이때 가장 많이 사용하는 API는 OpenWeather API이다.


☁️ 2. OpenWeather API 종류 쉽게 이해하기

API 종류 설명  
Current Weather API 지금 이 순간의 날씨 무료
5-Day / 3-Hour Forecast 5일 동안 3시간 간격 예보 무료
Air Pollution API 미세먼지(PM 2.5/PM 10) 무료
One Call API 3.0 현재 + 시간별 + 일별 + 미세먼지 묶인 패키지 유료

입문자는 Current Weather API 또는 5-Day Forecast API를 사용하는 것이 가장 쉽다.


🔑 3. OpenWeather API 키 발급받기

  1. https://openweathermap.org 방문
  2. 회원가입
  3. 로그인 후 API Keys 메뉴 이동
  4. 자동 생성된 API 키 복사

API가 발급된 화면

 

이 키를 API 요청 URL에 포함해야 정상적으로 데이터를 받을 수 있다.

예:

 

 


📍 4. 내 위치(위도/경도) 가져오기: Expo Location

React Native 기본만으로는 GPS 기능을 사용할 수 없다.
Expo 환경에서는 expo-location이라는 패키지를 이용하면 간단히 위치 정보를 얻을 수 있다.

설치

npx expo install expo-location

사용 방법

import * as Location from "expo-location";

const { granted } = await Location.requestForegroundPermissionsAsync();
if (!granted) return;

const {
  coords: { latitude, longitude } // { 위도, 경도}
} = await Location.getCurrentPositionAsync();
 

이 코드가 돌아가면 내 스마트폰의 현재 GPS 좌표를 얻을 수 있다.


🌦️ 5. 날씨 API 요청 보내기 (fetch)

위도/경도를 얻으면 API 요청 URL에 넣어서 날씨 데이터를 요청한다.

현재 날씨(Current Weather) URL 예시:

const url = `https://api.openweathermap.org/data/2.5/weather?lat=${latitude}&lon=${longitude}&appid=${API_KEY}&units=metric`;

요청 및 JSON 파싱:

const response = await fetch(url);
const json = await response.json();

console.log(json);

반환 데이터 예시

{
  "name": "Seoul",
  "main": {
    "temp": 18.5,
    "feels_like": 18.0
  },
  "weather": [
    { "description": "clear sky", "icon": "01d" }
  ]
}

이제 json.main.temp 같은 값을 UI에 표시하면 된다.


📅 6. 5일 예보를 받아 화면에 보여주기

나는 5-Day Forecast API를 사용했기 때문에 3시간 간격으로 40개의 데이터가 온다.

list: [
  { dt_txt: "2025-11-18 06:00:00", main: {...}, weather: [...] },
  { dt_txt: "2025-11-18 09:00:00", main: {...}, weather: [...] },
  ...
]

따라서 위 구조를 날짜별로 묶어 다음처럼 변환했다:

2025-11-18 → 평균 온도, 대표 날씨
2025-11-19 → 평균 온도, 대표 날씨
...
 
2025-11-18 → 평균 온도, 대표 날씨 2025-11-19 → 평균 온도, 대표 날씨 ...

 

날짜별 그룹화 개념:

const grouped = {};

json.list.forEach(item => {
  const date = item.dt_txt.split(" ")[0];
  if (!grouped[date]) grouped[date] = [];
  grouped[date].push(item);
});

이렇게 그룹화된 데이터를 state로 저장하면 된다.


📲 7. 화면에 날씨 정보 렌더링하기

단일 날씨(현재 날씨)

<Text>{Math.round(temp)}°</Text>
<Text>{description}</Text>

5일 예보 → 가로 스크롤 UI

<ScrollView horizontal pagingEnabled>
  {days.map((day, index) => (
    <View key={index}>
      <Text>{Math.round(day.temp)}°</Text>
      <Text>{day.description}</Text>
      <Text>{day.date}</Text>
    </View>
  ))}
</ScrollView>

핵심 포인트

  • horizontal -> 각 날씨 카드들을 가로로 리스트화
  • pagingEnabled → 날씨 카드가 한 페이지씩 넘어감


🧩 8. 전체 흐름 요약

단계 설명
위치 권한 요청 스마트폰 GPS 사용 허용 받기
위도·경도 확인 Location API로 좌표 획득
날씨 API 요청 OpenWeather 호출하여 JSON 획득
데이터 가공 필요한 값만 추출
UI 렌더링 ScrollView, Text 컴포넌트로 표시

이 다섯 단계만 이해하면 어떤 날씨 앱도 구현할 수 있다.

 

첨부한 이미지 풀코드

더보기
더보기
더보기
export default function App() {
  const [city, setCity] = useState("Loading...");
  const [days, setDays] = useState([]);
  const [ok, setOk] = useState(true);

  const getWeather = async () => {
    console.log("권한 체크 중…");

    // 1) 위치 권한 요청
    const { granted } = await Location.requestForegroundPermissionsAsync();
    console.log("granted:", granted);

    if (!granted) {
      setOk(false);
      return;
    }

    // 2) 현재 위치
    const {
      coords: { latitude, longitude }
    } = await Location.getCurrentPositionAsync({
      accuracy: Location.Accuracy.High,
    });

    console.log("위치:", latitude, longitude);

    // 3) reverse geocode → city
    const geo = await Location.reverseGeocodeAsync(
      { latitude, longitude },
      { useGoogleMaps: false }
    );
    setCity(geo[0].city);

    // 4) 5일 예보
    const url = `https://api.openweathermap.org/data/2.5/forecast?lat=${latitude}&lon=${longitude}&appid=${API_KEY}&units=metric`;
    const response = await fetch(url);
    const json = await response.json();

    // 5) 날짜별 그룹화
    const grouped = {};

    json.list.forEach(item => {
      const date = item.dt_txt.split(" ")[0];  
      if (!grouped[date]) {
        grouped[date] = [];
      }
      grouped[date].push(item);
    });

    // 날짜별 평균 temp & 대표 description 생성
    const result = Object.keys(grouped).map(date => {
      const entries = grouped[date];

      const avgTemp =
        entries.reduce((sum, entry) => sum + entry.main.temp, 0) /
        entries.length;

      const descriptions = entries.map(e => e.weather[0].description);
      const modeDescription = descriptions.sort(
        (a, b) =>
          descriptions.filter(v => v === a).length -
          descriptions.filter(v => v === b).length
      ).pop();
      console.log(modeDescription);
      return {
        date,
        temp: avgTemp,
        description: modeDescription,
      };
    });

    setDays(result);
  };

  useEffect(() => {
    getWeather();
  }, []);

  return (
    <View style={styles.container}>
      <View style={styles.city}>
        <Text style={styles.cityName}>{city}</Text>
      </View>

      <ScrollView
        pagingEnabled
        horizontal
        showsHorizontalScrollIndicator={false}
        contentContainerStyle={styles.weather}
      >
        {days.length === 0 ? (
          <View style={styles.day}>
            <ActivityIndicator color="black" size="large" style={{ marginTop: 100 }} />
          </View>
        ) : (
          days.map((item, index) => (
            <View style={styles.day} key={index}>
              <View style={{ flexDirection: "row", alignItems: "center"}}>
                <Text style={styles.temp}>{Math.round(item.temp)}°</Text>
                <Fontisto name={icons[item.description]} size={24} color="black" />
              </View>
              <Text style={styles.description}>{item.description}</Text>
              <Text style={styles.date}>{item.date}</Text>
            </View>
          ))
        )}
      </ScrollView>
    </View>
  );
}

const styles = StyleSheet.create({
  container: { flex: 1, backgroundColor: "white" },
  city: { flex: 1, justifyContent: "center", alignItems: "center" },
  cityName: { fontSize: 68, fontWeight: "500" },
  weather: { backgroundColor: "white" },
  day: { width: SCREEN_WIDTH, alignItems: "center" },
  temp: { marginTop: 50, fontSize: 80 },
  description: { marginTop: -10, fontSize: 30, textTransform: "capitalize" },
  date: { marginTop: 10, fontSize: 20, color: "gray" }
});

 

🔗 참고 자료