VesselWheel

3시간 단위 5일치 OpenWeatherMap API를 활용한 데이터 호출 본문

Xcode Study

3시간 단위 5일치 OpenWeatherMap API를 활용한 데이터 호출

JasonYang 2024. 2. 13. 08:49

기존에 WeatherData를 활용한 현재 날씨 API는 위경도 혹은 도시지역의 현재 날씨 기준으로 실시간 기상예보이다.

    // MARK: - public Methods
    //현재 날씨정보 : LocationManager에서 위치정보를 받고, 위경도 API에 적용한 후, 날씨 데이터 값 복사 : 독립적인 인스턴스 생성
    //https://api.openweathermap.org/data/2.5/weather?lat=37.565534&lon=126.977895&appid=3a33f61058f414d02d09e88bfa83117c
    public func getLocationWeather(latitude: Double, longitude: Double, completion: @escaping(Result<WeatherData, NetworkError>) -> Void) {
        LocationManager.shared.setLocation(latitude: latitude, longitude: longitude)
        guard let currentLocation = LocationManager.shared.currentLocation else {
            return completion(.failure(.badLocation))
        }
        
        guard let url = URL.urlForWeatherForLocation(currentLocation, apiKey: apiKey) else {
            return completion(.failure(.badUrl))
        }
        performRequest(with: url, completion: completion)
    }
    
    // OpenWeatherMap에서 지원하는 도시 검색 사이트 https://openweathermap.org/find?q
    // 현재 날씨정보의 도시 기준, url test(인천) : https://api.openweathermap.org/data/2.5/weather?q=incheon&appid=3a33f61058f414d02d09e88bfa83117c
    public func getCityWeather(city: String, completion: @escaping(Result<WeatherData, NetworkError>) -> Void) {
        let formattedCity = city.replacingOccurrences(of: " ", with: "+")
        guard let url = URL.urlForWeatherFor(formattedCity, apiKey: apiKey) else {
            return completion(.failure(.badUrl))
        }
        performRequest(with: url, completion: completion)
    }

하지만 날씨앱의 3시간 단위 5일치(무료 API 기준)을 하려면, WeatherData 모델이 해당 API에 맞게금 변경을 해주어야 한다. 

3시간 단위 5일치 날씨예보 API call test : 

test API call : api.openweathermap.org/data/2.5/forecast?lat=37.4536&lon=126.7317&appid=3a33f61058f414d02d09e88bfa83117c

상기 기준 API call의 데이터 모델 :  ForecastWeatherData

더보기
// This file was generated from JSON Schema using quicktype, do not modify it directly.
// To parse the JSON, add this file to your project and do:
//
//   let forecastWeatherData = try? JSONDecoder().decode(ForecastWeatherData.self, from: jsonData)

import Foundation

// MARK: - ForecastWeatherData
struct ForecastWeatherData: Codable {
    let cod: String
    let message, cnt: Int
    let list: [List]
    let city: City
}

// MARK: - City
struct City: Codable {
    let id: Int
    let name: String
    let coord: Coord
    let country: String
    let population, timezone, sunrise, sunset: Int
}

// MARK: - Coord
struct Coord: Codable {
    let lat, lon: Double
}

// MARK: - List
struct List: Codable {
    let dt: Int
    let main: MainClass
    let weather: [Weather]
    let clouds: Clouds
    let wind: Wind
    let visibility: Int
    let pop: Double
    let sys: Sys
    let dtTxt: String
    let rain: Rain?

    enum CodingKeys: String, CodingKey {
        case dt, main, weather, clouds, wind, visibility, pop, sys
        case dtTxt
        case rain
    }
}

// MARK: - Clouds
struct Clouds: Codable {
    let all: Int
}

// MARK: - MainClass
struct MainClass: Codable {
    let temp, feelsLike, tempMin, tempMax: Double
    let pressure, seaLevel, grndLevel, humidity: Int
    let tempKf: Double

    enum CodingKeys: String, CodingKey {
        case temp
        case feelsLike
        case tempMin
        case tempMax
        case pressure
        case seaLevel
        case grndLevel
        case humidity
        case tempKf
    }
}

// MARK: - Rain
struct Rain: Codable {
    let the3H: Double

    enum CodingKeys: String, CodingKey {
        case the3H
    }
}

// MARK: - Sys
struct Sys: Codable {
    let pod: Pod
}

enum Pod: String, Codable {
    case d = "d"
    case n = "n"
}

// MARK: - Weather
struct Weather: Codable {
    let id: Int
    let main: MainEnum
    let description: Description
    let icon: Icon
}

enum Description: String, Codable {
    case brokenClouds = "broken clouds"
    case clearSky = "clear sky"
    case lightRain = "light rain"
    case overcastClouds = "overcast clouds"
}

enum Icon: String, Codable {
    case the01D = "01d"
    case the01N = "01n"
    case the04D = "04d"
    case the04N = "04n"
    case the10D = "10d"
    case the10N = "10n"
}

enum MainEnum: String, Codable {
    case clear = "Clear"
    case clouds = "Clouds"
    case rain = "Rain"
}

// MARK: - Wind
struct Wind: Codable {
    let speed: Double
    let deg: Int
    let gust: Double
}

ForecastWeatherData을 활용한 API 호출 매소드 

    // https://openweathermap.org/forecast5 5일치 3시간 단위 일기예보
    //test API call : api.openweathermap.org/data/2.5/forecast?lat=37.4536&lon=126.7317&appid=3a33f61058f414d02d09e88bfa83117c
    public func getForecastWeather(latitude: Double, longitude: Double, completion: @escaping(Result<ForecastWeatherData, NetworkError>) -> Void) {
        LocationManager.shared.setLocation(latitude: latitude, longitude: longitude)
        guard let currentLocation = LocationManager.shared.currentLocation else {
            return completion(.failure(.badLocation))
        }
        
        guard let url = URL.urlForForecastForLocation(currentLocation, apiKey: apiKey) else {
            return completion(.failure(.badUrl))
        }
        performRequestForecast(with: url, completion: completion)
    }

URL을 extension으로 구분하여 코드의 가독성을 높였다. 

더보기
//
//  URL+Extension.swift
//  Weather777
//
//  Created by Jason Yang on 2/13/24.
//

import Foundation
import CoreLocation


extension URL {
    
    //위, 경도 API call
    //https://openweathermap.org/current
    static func urlForWeatherForLocation(_ currentLocation: CLLocationCoordinate2D, apiKey: String) -> URL? {
        guard let url = URL(string: "https://api.openweathermap.org/data/2.5/weather?lat=\(currentLocation.latitude)&lon=\(currentLocation.longitude)&appid=\(apiKey)") else {
            return nil
        }
        return url
    }
    
    //도시 기준 API call
    //https://openweathermap.org/current
    static func urlForWeatherFor(_ city: String, apiKey: String) -> URL? {
        guard let url = URL(string: "https://api.openweathermap.org/data/2.5/weather?q=\(city)&appid=\(apiKey)") else {
            return nil
        }
        return url
    }
    
    //5일치 3시간 단위 날씨 예보 API call
    //https://openweathermap.org/forecast5
    static func urlForForecastForLocation(_ currentLocation: CLLocationCoordinate2D, apiKey: String) -> URL? {
        guard let url = URL(string: "https://api.openweathermap.org/data/2.5/forecast?lat=\(currentLocation.latitude)&lon=\(currentLocation.longitude)&appid=\(apiKey)") else {
            return nil
        }
        return url
    }
    
//    //Air Pollution API
//    //https://openweathermap.org/api/air-pollution
//    static func urlForAirPollutionForLocation(_ currentLocation: CLLocationCoordinate2D, apiKey: String) -> URL? {
//        guard let url = URL(string: "http://api.openweathermap.org/data/2.5/air_pollution?lat=\(currentLocation.latitude)&lon=\(currentLocation.longitude)&appid=\(apiKey)") else {
//            return nil
//        }
//        return url
//    }
//    
//    //Geocoding API
//    //https://openweathermap.org/api/geocoding-api
//    static func urlForGeoDirect(_ city: String, stateCode: String, countryCode: String, limit: Int, apiKey: String) -> URL? {
//        guard let url = URL(string: "http://api.openweathermap.org/geo/1.0/direct?q=\(city),\(stateCode),\(countryCode)&limit=\(limit)&appid=\(apiKey)") else {
//            return nil
//        }
//        return url
//    }
//    
//    //Weather Maps 1.0
//    //https://openweathermap.org/api/weathermaps
//    static func urlForMapLayer(_ layer: String, z: Int, x: Int, y: Int, apiKey: String) -> URL? {
//        guard let url = URL(string: "https://tile.openweathermap.org/map/\(layer)/\(z)/\(x)/\(y).png?appid=\(apiKey)") else {
//            return nil
//        }
//        return url
//    }
    
}

- OpenWeatherMap 은 현재날씨, 5일치 날씨예보, 미세먼지, 지역, 날씨맵 API도 지원한다.