OpenWeather API로 현재 날씨와 미세먼지 다루기
OpenWeather API를 사용해 현재 날씨와 대기 오염 정보를 프로젝트에 적용하는 방법을 알아보자
1. 글의 목적
인천광역시청 웹사이트를 클론하면서 작성해두었던 헤더 영역의 날씨부분을 실제 데이터 기반으로 동작할 수 있도록 업데이트 해야했다. OpenWeather API를 사용해 현재 날씨, 기온, 미세먼지 농도를 가져오는 과정을 다음 작업 시에도 참고할 수 있도록 작업 기록을 정리 해보려고 한다.
2. API Key 발급받기
OpenWeather API를 사용하기 위해서는 우선 API Key가 필요하다. 회원가입 후 My API Keys
메뉴에서 확인할 수 있다. 이 Key는 모든 API를 호출 시 필수적으로 포함되어야 하는 값이므로 잘 보관해두자.
3. 현재 날씨 데이터 가져오기
3-1. 기본 날씨 정보 요청하기
<https://api.openweathermap.org/data/2.5/weather?q=>{city name}&appid={API key}
OpenWeather API는 도시 이름이나 위도/경도 좌표를 기반으로 날씨 정보를 제공한다. 도시 이름으로 요청하는 경우에는 위와 같은 URL을 사용한다. 작업에 필요한 데이터는 인천광역시의 실시간 날씨였기 때문에 city name
에는 incheon
을 작성해 주었다. 그러면 아래와 같은 데이터들을 얻을 수 있다. 이후 필요한 값만 추출해 사용하면 된다.
// 응답 데이터 파싱
const weatherData = await weatherResponse.json();
// 필요한 데이터 추출
const weatherMainCode = weatherData.weather[0].main;
const temperature = weatherData.main.temp;
3-2. K(켈빈) → °C(섭씨) 온도 단위 변환하기
API에서 제공되는 temp 값
을 보면, 섭씨(°C)도 화씨(°F)도 아닌 상당히 큰 숫자가 기록되어 있음을 알 수 있는데, OpenWeather API에서 받은 temp 값은 켈빈(K)
온도로 제공된다고 한다. 때문에 섭씨로 표기하기 위해서는 켈빈을 섭씨로 변환하는 별도의 과정을 거쳐야 한다.
const kelvinToCelsius = (kelvin) => Number((kelvin - 273.15).toFixed(1));
// 예: 288.08K → 14.9°C
3-3. 기존 날씨 아이콘과 현재 날씨 매칭하기
전체 데이터 중 weather
를 살펴보면, 현재 날씨의 명칭과 코드, 아이콘 등을 제공하는데, 해당 속성에서 주어진 값들을 그대로 사용한다면 간단한 작업이 되겠지만, 기본으로 제공해주는 아이콘이 기존 사이트 디자인과 조화롭지 않을 수 있다. 때문에 보유하고 있는 에셋이 날씨 정보에 따라 표현될 수 있도록 매칭하는 과정을 거쳐야 했다.
weather
에서 제공해 주는 값 중, main의 키워드들을 기준으로 코드를 작성해보기로 했다. main에서 생성될 수 있는 키워드들은 Clear, Clouds, Atmosphere, Drizzle, Rain, Thunderstorm, Snow
총 7가지였고, 기존 아이콘은 맑음, 구름많음, 흐림, 약간 비, 비, 눈
이렇게 6가지가 있다.
const weatherConditions = {
Clear: [1, "맑음"],
Clouds: [2, "구름많음"],
Atmosphere: [3, "흐림"],
Drizzle: [4, "약간 비"],
Rain: [5, "비"],
Thunderstorm: [5, "비"],
Snow: [6, "눈"],
};
기존 아이콘 파일의 네이밍이 weather_0n
.png 순서대로 작성되어있기 때문에, 위와 같이 제공받는 날씨 키워드에 따라 아이콘 이미지가 매칭될 수 있도록 매핑 객체를 작성해주었다.
const currentWeather = document.querySelector(".current-weather > a");
currentWeather.insertAdjacentHTML(
"afterbegin",
`<img class="weather-icon" data-weather-icon src="" alt />`
);
const weatherIcon = `weather_0${weatherConditions[weatherMainCode][0]}.png`;
const weatherAlt = weatherConditions[weatherMainCode][1];
// DOM 업데이트
select("[data-weather-icon]").src = `assets/images/weather/${weatherIcon}`;
select("[data-weather-icon]").alt = weatherAlt;
이어서 페이지 로딩 시 잠깐 보이는 빈 이미지나 임시 이미지가 보기 좋지 않아, 자바스크립트 상에서 이미지 요소를 동적으로 생성하고 데이터를 주입하는 방식으로 작업을 완성했다. 동시에 이미지의 alt 값
을 한글 날씨 키워드로 업데이트하여, 스크린 리더가 현재 날씨를 설명하는데 도움이 되도록 구현했다.
4. 대기 오염 데이터 가져오기
4-1. 대기 오염 정보 요청하기
<http://api.openweathermap.org/data/2.5/air_pollution?lat={lat}&lon={lon}&appid=>{API key}
대기 오염과 관련한 데이터를 불러오기 위해서는 URL에 대기 정보를 얻고자 하는 지역의 위도와 경도를 같이 작성해줘야 한다. 위도/경도는 날씨 데이터를 중 coord
부분에서 확인할 수 있다. (city name “Incheon”
을 기준으로는 lat(위도): 37.45, lon(경도): 126.4161
가 나온다.)
대기 오염 데이터 중 components
부분을 살펴보면 여러가지 대기와 관련된 정보를 확인할 수 있는데, 그 중에서 pm10
은 미세먼지
, pm2_5
는 초미세먼지
에 대한 정보를 제공하고 있다.
4-2. pm10, pm2_5 기반으로 미세먼지 등급 판별하기
미세먼지는 초미세먼지(PM-2.5)와 미세먼지(PM-10)으로 구분된다. 초미세먼지(PM-2.5)는 직경이 2.5㎛이하인 먼지이며, 미세먼지(PM-10)은 직경이 10㎛이하인 먼지다. 아래 조건들을 기반으로 매우좋음 / 좋음 / 보통 / 나쁨 / 매우 나쁨 등 총 5가지 등급의 공기질을 판별한다.
// 미세먼지 등급 기준 정의
const DUST_LEVEL_CRITERIA = {
MISE: [
{ max: 20, level: "매우좋음" },
{ max: 50, level: "좋음" },
{ max: 100, level: "보통" },
{ max: 200, level: "나쁨" },
{ max: Infinity, level: "매우나쁨" },
],
CHOMISE: [
{ max: 10, level: "매우좋음" },
{ max: 25, level: "좋음" },
{ max: 50, level: "보통" },
{ max: 75, level: "나쁨" },
{ max: Infinity, level: "매우나쁨" },
],
};
const getDustLevel = (value, criteria) => {
return criteria.find(({ max }) => value < max)?.level;
};
const pollutionData = await pollutionResponse.json();
// 필요한 데이터 추출
const pm10 = pollutionData.list[0].components.pm10;
const pm25 = pollutionData.list[0].components.pm2_5;
// 미세먼지 등급 계산
const miseLevel = getDustLevel(pm10, DUST_LEVEL_CRITERIA.MISE);
const chomiseLevel = getDustLevel(pm25, DUST_LEVEL_CRITERIA.CHOMISE);
// DOM 업데이트
select("[data-dust-mise]").textContent = miseLevel;
select("[data-dust-chomise]").textContent = chomiseLevel;
5. 최종 구현 코드
// DOM 요소 선택 함수
const select = (selector) => document.querySelector(selector);
// 켈빈 값 섭씨 전환 함수
const kelvinToCelsius = (kelvin) => Number((kelvin - 273.15).toFixed(1));
// 현재 미세먼지 농도 판별 함수
const getDustLevel = (value, criteria) => {
return criteria.find(({ max }) => value < max)?.level;
};
// 날씨 아이콘 매칭 데이터
const weatherConditions = {
Clear: [1, "맑음"],
Clouds: [2, "구름많음"],
Atmosphere: [3, "흐림"],
Drizzle: [4, "약간 비"],
Rain: [5, "비"],
Thunderstorm: [5, "비"],
Snow: [6, "눈"],
};
// 미세먼지 등급 기준 정의 데이터
const DUST_LEVEL_CRITERIA = {
MISE: [
{ max: 20, level: "매우좋음" },
{ max: 50, level: "좋음" },
{ max: 100, level: "보통" },
{ max: 200, level: "나쁨" },
{ max: Infinity, level: "매우나쁨" },
],
CHOMISE: [
{ max: 10, level: "매우좋음" },
{ max: 25, level: "좋음" },
{ max: 50, level: "보통" },
{ max: 75, level: "나쁨" },
{ max: Infinity, level: "매우나쁨" },
],
};
// API 설정
const API_KEY = "API key";
const WEATHER_API = "<https://api.openweathermap.org/data/2.5/weather>";
const POLLUTION_API = "<https://api.openweathermap.org/data/2.5/air_pollution>";
async function getWeatherData() {
try {
// 날씨 정보와 대기오염 정보를 동시에 가져오기
const [weatherResponse, pollutionResponse] = await Promise.all([
fetch(`${WEATHER_API}?q=incheon&appid=${API_KEY}`),
fetch(`${POLLUTION_API}?lat=37.45&lon=126.4161&appid=${API_KEY}`),
]);
if (!weatherResponse.ok || !pollutionResponse.ok) {
throw new Error(`
날씨 API 상태: ${weatherResponse.status}
대기오염 API 상태: ${pollutionResponse.status}
`);
}
// 응답 데이터 파싱
const weatherData = await weatherResponse.json();
const pollutionData = await pollutionResponse.json();
// 필요한 데이터 추출
const weatherMainCode = weatherData.weather[0].main;
const temperature = kelvinToCelsius(weatherData.main.temp);
const pm10 = pollutionData.list[0].components.pm10;
const pm25 = pollutionData.list[0].components.pm2_5;
// 날씨 아이콘 업데이트
select(".current-weather > a").insertAdjacentHTML(
"afterbegin",
`<img class="weather-icon" data-weather-icon src="" alt />`
);
const weatherIcon = `weather_0${weatherConditions[weatherMainCode][0]}.png`;
const weatherAlt = weatherConditions[weatherMainCode][1];
// 미세먼지 등급 계산
const miseLevel = getDustLevel(pm10, DUST_LEVEL_CRITERIA.MISE);
const chomiseLevel = getDustLevel(pm25, DUST_LEVEL_CRITERIA.CHOMISE);
// DOM 업데이트
select("[data-weather-icon]").src = `assets/images/weather/${weatherIcon}`;
select("[data-weather-icon]").alt = weatherAlt;
select("[data-weather-temper]").textContent = temperature;
select("[data-dust-mise]").textContent = miseLevel;
select("[data-dust-chomise]").textContent = chomiseLevel;
} catch (error) {
console.error("날씨 정보 조회 실패:", error);
}
}
DOM 요소 선택 함수 및 여러가지 유틸리티 함수들을 정의한다.
날씨 아이콘 매칭 데이터와 미세먼지 등급 기준 데이터를 정의한다.
OpenWeatherMap API 키와 API 엔드포인트 URL을 설정한다.
날씨 데이터와 대기오염 데이터를 동시에 API에서 가져오고, 필요한 정보를 추출한다.
날씨 아이콘 이미지 요소를 동적으로 생성하고 alt 속성을 업데이트한다.
미세먼지 등급을 계산하고 DOM을 업데이트한다.
페이지 로드 시 getWeatherData 함수를 호출하여 정보를 초기화한다.