일일일코_장민기 2024. 5. 12. 09:53
728x90

https://www.data.go.kr/data/15084084/openapi.do#

 

기상청_단기예보 ((구)_동네예보) 조회서비스

초단기실황, 초단기예보, 단기((구)동네)예보, 예보버전 정보를 조회하는 서비스입니다. 초단기실황정보는 예보 구역에 대한 대표 AWS 관측값을, 초단기예보는 예보시점부터 6시간까지의 예보를,

www.data.go.kr

 

오늘은 공공데이터인 기상청의 단기예보 데이터를 가져와서 사용하기 편하게 가공한 뒤 출력하는 작업을 진행했다.

 

블로그나 이런 곳의 글을 보고 만든 것이 아니라 공식문서를 보고 직접(Chat GPT와 함께) 작성한 것에 의의를 둔다

 

아무튼 해당 API에는 초단기 / 단기 / 중기 예보가 있다.

초단기인 경우, 1시간 단위로 데이터가 제공되지만 뇌우와 강우 데이터만 제공된다.

중기일 경우, N일 후의 기온이나 강우 확률 등의 데이터가 제공된다.

따라서 "오늘 뭐 입지"의 취지에 맞춰서 단기 예보 데이터를 선택했다.

 

공식 문서가 엄청 잘 쓰여있어서 사용하기 편했다.

공식 문서의 액셀 파일이 있는데 크게 5군데만 고려하면 된다.

 

 

격자 X와 Y는 각각 경도와 위도다.

저 수치를 기반으로 지역을 설정하고, 시간과 날짜를 지정하여, 해당 지역의 날씨 데이터를 불러온다.

날짜는 현재 시점 이전만 가능하고, 시간은 특정 시간대만 가능하다.

 

전체데이터 확인

더보기

DB

CREATE TABLE weatherAreavo (
    id SERIAL PRIMARY KEY,
    country VARCHAR(50) NOT NULL,
    area VARCHAR(50) NOT NULL,
    nx INTEGER NOT NULL,
    ny INTEGER NOT NULL
);

 

VO(DB에서 데이터를 뽑을 때 사용) - 이후 확장 가능성을 고려하여 현재 쓰지 않는 데이터도 그냥 불러왔다.

package org.weather;

import jakarta.persistence.*;
import jakarta.validation.constraints.NotNull;
import jakarta.validation.constraints.Size;
import lombok.Getter;
import lombok.Setter;
import org.hibernate.annotations.ColumnDefault;

@Getter
@Setter
@Entity
@Table(name = "weatherareavo")
public class Weatherareavo {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    @ColumnDefault("nextval('weatherareavo_id_seq'")
    @Column(name = "id", nullable = false)
    private Integer id;

    @Size(max = 50)
    @NotNull
    @Column(name = "country", nullable = false, length = 50)
    private String country;

    @Size(max = 50)
    @NotNull
    @Column(name = "area", nullable = false, length = 50)
    private String area;

    @NotNull
    @Column(name = "nx", nullable = false)
    private Integer nx;

    @NotNull
    @Column(name = "ny", nullable = false)
    private Integer ny;

}

 

voDTO(확장된다면 Country도 활용될 것이다)

package org.weather;

import jakarta.persistence.Column;
import jakarta.validation.constraints.NotNull;
import jakarta.validation.constraints.Size;
import lombok.Data;
import lombok.Value;
import org.springframework.format.annotation.DateTimeFormat;

import java.io.Serializable;
import java.util.Date;

/**
 * DTO for {@link Weatherareavo}
 */
@Data
@Value
public class WeatherareavoDTO implements Serializable {

    @NotNull
    String base_date;

    @NotNull
    String base_time;

    @NotNull
    @Size(max = 50)
    String country;

    @NotNull
    @Size(max = 50)
    String area;
}

 

컨트롤러

package org.weather.api;

import org.springframework.beans.factory.annotation.Value;
import lombok.extern.slf4j.Slf4j;
import org.json.JSONArray;
import org.json.JSONException;
import org.json.JSONObject;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.servlet.ModelAndView;
import org.weather.WeatherDataDTO;
import org.weather.WeatherareavoDTO;

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.net.HttpURLConnection;
import java.net.URL;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;

@Slf4j
@Controller
public class WeatherApiController {

    private final WeatherApiService weatherApiService;

    private WeatherApiController(WeatherApiService weatherApiService){
        this.weatherApiService = weatherApiService;
    }

    @Value("${weather.api.key}")
    String weatherApiKey;

    @GetMapping("/")
    public String main(){
        return "Main";
    }

    @PostMapping("/weatherRequest")
    public ModelAndView weatherRequest(WeatherareavoDTO weatherareavoDTO){

        ModelAndView modelAndView = new ModelAndView();

        //위도와 경도 가져오기
        Map position = weatherApiService.weatherRequest(weatherareavoDTO.getArea());

        try {

        //api 요청을 위한 url 구성
        String stringURL =
            "http://apis.data.go.kr/1360000/VilageFcstInfoService_2.0/getVilageFcst" +
                "?serviceKey=" + weatherApiKey +
                "&numOfRows=10" +
                "&pageNo=1" +
                "&dataType=JSON" +
                "&base_date=" + weatherareavoDTO.getBase_date() +
                "&base_time=" + weatherareavoDTO.getBase_time() +
                "&nx="+ position.get("nx") +
                "&ny="+ position.get("ny");

            //url로 HTTP Request
            URL url = new URL(stringURL);
            HttpURLConnection connection = (HttpURLConnection) url.openConnection();
            connection.setRequestMethod("GET");

            //응답코드 확인
            //log.info("Response Code: {}", connection.getResponseCode());

            //응답 처리
            BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(connection.getInputStream()));
            String response = String.join("/n", bufferedReader.lines().toList());
            bufferedReader.close();
            connection.disconnect();
            
            //JSON 데이터 출력(원본)
            //log.info("Response: {}", response);
            
            //JSON 데이터에서 필요한 데이터 추출
            JSONObject jsonObject = new JSONObject(response);
            JSONObject items = jsonObject.getJSONObject("response").getJSONObject("body").getJSONObject("items");
            JSONArray itemsArray = items.getJSONArray("item");

            //DTO로 구성된 List 생성
            List<WeatherDataDTO> weatherDataDTOList = new ArrayList<>();
            for (int i = 0; i < itemsArray.length(); i++) {
                JSONObject item = itemsArray.getJSONObject(i);
                String baseTime = item.getString("baseTime");
                String category = item.getString("category");
                String fcstValue = item.getString("fcstValue");

                //DTO 데이터 재구성
                WeatherDataDTO weatherDataDTO = new WeatherDataDTO();
                    weatherDataDTO.setBaseTime(baseTime);
                    weatherDataDTO.setCategory(category);
                    weatherDataDTO.setFcstValue(category, fcstValue);
                weatherDataDTOList.add(weatherDataDTO);
            }

            //가공된 데이터 출력
            log.info("WeatherDataDTOList: {}", weatherDataDTOList);
            modelAndView.addObject("weatherDataDTOList", weatherDataDTOList);

        } catch (IOException | JSONException e) {
            throw new RuntimeException(e);
        }

        modelAndView.setViewName("Main");
        return modelAndView;
    }
}

 

서비스

package org.weather.api;

import org.springframework.stereotype.Service;
import org.weather.Weatherareavo;

import java.util.HashMap;
import java.util.Map;

@Service
public class WeatherApiService {

    public final WeatherApiRepository weatherApiRepository;

    public WeatherApiService(WeatherApiRepository weatherApiRepository){
        this.weatherApiRepository = weatherApiRepository;
    }

    public Map weatherRequest(String area) {
        Weatherareavo weatherareavo = weatherApiRepository.findByArea(area);
        Map map = new HashMap();
            map.put("nx", weatherareavo.getNx());
            map.put("ny", weatherareavo.getNy());
        return map;
    }
}

- 액셀 파일의 데이터를 전부 확인해보지는 못했는데 만약 데이터 제공 범위를 확장했을 때 area가 이름이 겹치는 곳이 있다면 country까지 써야 할 것이다

 

Repository

package org.weather.api;

import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.stereotype.Repository;
import org.weather.Weatherareavo;

@Repository
public interface WeatherApiRepository extends JpaRepository<Weatherareavo, Integer> {

    Weatherareavo findByArea(String area);
}

 

dataDTO

package org.weather;

import jakarta.validation.constraints.NotNull;
import jakarta.validation.constraints.Size;
import lombok.*;

import java.io.Serializable;

/**
 * DTO for {@link Weatherareavo}
 */
@Data
public class WeatherDataDTO implements Serializable {

    @NotNull
    String baseTime ;

    @NotNull
    String category;

    @NotNull
    String fcstValue;

    public void setCategory(String category){
        this.category = switch (category) {
            case "TMP" -> "1시간 기온";
            case "UUU" -> "풍속(동서성분)";
            case "VVV" -> "풍속(남북성분)";
            case "VEC" -> "풍향";
            case "WSD" -> "풍속";
            case "SKY" -> "하늘상태";
            case "PTY" -> "강수형태";
            case "POP" -> "강수확률";
            case "WAV" -> "파고";
            case "PCP" -> "1시간 강수량";
            default -> throw new IllegalStateException("Unexpected value: " + category);
        };
    };

    public void setFcstValue(String category, String fcstValue){
        this.fcstValue = switch (category) {
            case "TMP" -> fcstValue + "℃";
            case "UUU", "WSD", "VVV" -> fcstValue + "m/s";
            case "VEC" -> {
                            double value = Double.parseDouble(fcstValue);
                            double convertedValue = (value + 22.5 * 0.5) / 22.5;
                            int directionIndex = (int) Math.floor(convertedValue);
                            String[] directions = {"N", "NNE", "NE", "ENE", "E", "ESE", "SE", "SSE", "S", "SSW", "SW", "WSW", "W", "WNW", "NW", "NNW", "N"};
                            yield directions[directionIndex];
                          }
            case "SKY" -> {
                            int value = Integer.parseInt(fcstValue);
                            yield switch (value){
                                case 1, 2, 3, 4, 5 -> "맑음";
                                case 6, 7, 8 -> "구름많음";
                                case 9, 10 -> "흐림";
                                default -> throw new IllegalStateException("Unexpected value: " + value);
                            };
                          }
            case "PTY" -> { if (fcstValue.equals("0")) {
                                yield "강수없음";
                            } else {
                                yield fcstValue;
                            }}
            case "POP" -> fcstValue + "%";
            case "WAV" -> fcstValue + "M";
            case "PCP" -> { if (fcstValue.equals("강수없음")) {
                                yield fcstValue;
                            } else {
                                yield fcstValue + "mm";
                            }}
            default -> throw new IllegalStateException("Unexpected value: " + category);
        };
    }
}

 

jsp

<%@ page language="java" contentType="text/html; charset=UTF-8" pageEncoding="UTF-8"%>
<%@ taglib uri="http://java.sun.com/jsp/jstl/core" prefix="c" %>
<%@ taglib uri="http://java.sun.com/jsp/jstl/functions" prefix="fn" %>
<%@ taglib prefix="form" uri="http://www.springframework.org/tags/form" %>

<!DOCTYPE html>
<html lang="ko">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
    <script src="https://code.jquery.com/jquery-3.6.4.min.js"></script>
    <link href="https://cdn.jsdelivr.net/npm/bootstrap@5.1.3/dist/css/bootstrap.min.css" rel="stylesheet" integrity="sha384-1BmE4kWBq78iYhFldvKuhfTAU6auU8tT94WrHftjDbrCEXSU1oBoqyl2QvZ6jIW3" crossorigin="anonymous">
    <script type="text/javascript" src="<c:url value='/resources/js/WeatherMain.js'/>"></script>
</head>
<body>

<h1>Weather Main Page</h1>

<p>
    <table>
        <thead>
            <tr>
                <th>항목</th>
                <th>값</th>
            </tr>
        </thead>
        <tbody>
            <c:forEach var="data" items="${weatherDataDTOList}">
                <tr>
                    <th><c:out value="${data.category}"/></th>
                    <th><c:out value="${data.fcstValue}"/></th>
                </tr>
            </c:forEach>
        </tbody>
    </table>
</p>

<p>
    <form id="weatherRequest" action="<c:url value='/weatherRequest'/>" method="post">
        <input type="hidden" id="base_date" name="base_date">
        <input type="hidden" id="base_time" name="base_time">
        지역(경기도 한정):
        <select id="area" name="area">
            <option value="수원시장안구">수원시장안구</option>
            <option value="수원시권선구">수원시권선구</option>
            <option value="수원시팔달구">수원시팔달구</option>
            <option value="수원시영통구">수원시영통구</option>
            <option value="성남시수정구">성남시수정구</option>
            <option value="성남시중원구">성남시중원구</option>
            <option value="성남시분당구">성남시분당구</option>
            <option value="의정부시">의정부시</option>
            <option value="안양시만안구">안양시만안구</option>
            <option value="안양시동안구">안양시동안구</option>
            <option value="부천시원미구">부천시원미구</option>
            <option value="부천시소사구">부천시소사구</option>
            <option value="부천시오정구">부천시오정구</option>
            <option value="광명시">광명시</option>
            <option value="평택시">평택시</option>
            <option value="동두천시">동두천시</option>
            <option value="안산시상록구">안산시상록구</option>
            <option value="안산시단원구">안산시단원구</option>
            <option value="고양시덕양구">고양시덕양구</option>
            <option value="고양시일산동구">고양시일산동구</option>
            <option value="고양시일산서구">고양시일산서구</option>
            <option value="과천시">과천시</option>
            <option value="구리시">구리시</option>
            <option value="남양주시">남양주시</option>
            <option value="오산시">오산시</option>
            <option value="시흥시">시흥시</option>
            <option value="군포시">군포시</option>
            <option value="의왕시">의왕시</option>
            <option value="하남시">하남시</option>
            <option value="용인시처인구">용인시처인구</option>
            <option value="용인시기흥구">용인시기흥구</option>
            <option value="용인시수지구">용인시수지구</option>
            <option value="파주시">파주시</option>
            <option value="이천시">이천시</option>
            <option value="안성시">안성시</option>
            <option value="김포시">김포시</option>
            <option value="화성시">화성시</option>
            <option value="광주시">광주시</option>
            <option value="양주시">양주시</option>
            <option value="포천시">포천시</option>
            <option value="여주시">여주시</option>
            <option value="연천군">연천군</option>
            <option value="가평군">가평군</option>
            <option value="양평군">양평군</option>
        </select>
        <input type="submit" value="확인">
    </form>
</p>

<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.1.3/dist/js/bootstrap.bundle.min.js"
        integrity="sha384-ka7Sk0Gln4gmtz2MlQnikT1wXgYsOg+OMhuP+IlRH9sENBO0LRn5q+8nbTov4+1p"
        crossorigin="anonymous"></script>
</body>
</html>

 

js

$(function(){

    // 오늘 날짜 데이터 전송
    $("#weatherRequest").on("submit", function (){
        var base_date = $("#base_date");
        var base_time = $("#base_time");

        /** 현재 시간 및 날짜 설정 */
        // 오늘 날짜 및 시간 설정
        const today = new Date();
        const year = today.getFullYear();
        const month = (today.getMonth()+1).toString().padStart(2, "0");
        const day = today.getDate().toString().padStart(2, "0");
        const formattedDate = year+month+day;
        const hour = today.getHours()
        const now = today.toLocaleTimeString();

        // 오늘 날짜 및 시간 출력
        console.log("오늘 날짜: " + formattedDate);
        console.log("현재 시간: " + now);

        /** 최신 날짜 및 시간 지정 */
        let dateForAPI = formattedDate;
        let timeForAPI;

        switch (hour){
            case 0:
            case 1:
                const yesterday = new Date(today);
                yesterday.setDate(yesterday.getDate()-1);

                const yesterday_year = yesterday.getFullYear();
                const yesterday_month = (yesterday.getMonth()+1).toString().padStart(2, "0");
                const yesterday_day = yesterday.getDate().toString().padStart(2, "0");
                dateForAPI = yesterday_year+yesterday_month+yesterday_day;
                timeForAPI = "2300";
                break;
            case 2:
            case 3:
            case 4:
                timeForAPI = "0200";
                break;
            case 5:
            case 6:
            case 7:
                timeForAPI = "0500";
                break;
            case 8:
            case 9:
            case 10:
                timeForAPI = "0800";
                break;
            case 11:
            case 12:
            case 13:
                timeForAPI = "1100";
                break;
            case 14:
            case 15:
            case 16:
                timeForAPI = "1400";
                break;
            case 17:
            case 18:
            case 19:
                timeForAPI = "1700";
                break;
            case 20:
            case 21:
            case 22:
                timeForAPI = "2000";
                break;
            default:
                timeForAPI = "2300";
        }

        // 최신 날짜와 시간 출력
        console.log("최신 날짜: " + dateForAPI);
        console.log("최신 시간: " + timeForAPI);

        // 최신 날짜와 시간 전송
        base_date.val(dateForAPI);
        base_time.val(timeForAPI);

    })

}) // $(function()

컨트롤러

//위도와 경도 가져오기
Map position = weatherApiService.weatherRequest(weatherareavoDTO.getArea());

- jsp에서 입력된 지역을 DTO로 가져오고 서비스로 넘긴다

- 서비스로부터는 경도와 위도를 제공받는다

String stringURL =
    "http://apis.data.go.kr/1360000/VilageFcstInfoService_2.0/getVilageFcst" +
        "?serviceKey=" + weatherApiKey +
        "&numOfRows=10" +
        "&pageNo=1" +
        "&dataType=JSON" +
        "&base_date=" + weatherareavoDTO.getBase_date() +
        "&base_time=" + weatherareavoDTO.getBase_time() +
        "&nx="+ position.get("nx") +
        "&ny="+ position.get("ny");

- JSP에서 넘어온 날짜 / 시간 + DB에서 가져온 경도와 위도를 통해 url 구성

 

//url로 HTTP Request
URL url = new URL(stringURL);
HttpURLConnection connection = (HttpURLConnection) url.openConnection();
connection.setRequestMethod("GET");

- 서버 단에서 바로 데이터를 요청한다

- 옛날 구글로그인할 때 만들엇던 HttpHeaders보다 이해하기 쉽고 간단하다

 

//응답 처리
BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(connection.getInputStream()));
String response = String.join("/n", bufferedReader.lines().toList());

- 받아온 데이터를 BufferedReader를 통해 String 데이터로 가져온다(JSON 형식)

 

//JSON 데이터에서 필요한 데이터 추출
JSONObject jsonObject = new JSONObject(response);
JSONObject items = jsonObject.getJSONObject("response").getJSONObject("body").getJSONObject("items");
JSONArray itemsArray = items.getJSONArray("item");

- JSON 데이터에서 필요한 데이터를 뽑는다

 

//DTO로 구성된 List 생성
List<WeatherDataDTO> weatherDataDTOList = new ArrayList<>();
for (int i = 0; i < itemsArray.length(); i++) {
    JSONObject item = itemsArray.getJSONObject(i);
    String baseTime = item.getString("baseTime");
    String category = item.getString("category");
    String fcstValue = item.getString("fcstValue");

    //DTO 데이터 재구성
    WeatherDataDTO weatherDataDTO = new WeatherDataDTO();
        weatherDataDTO.setBaseTime(baseTime);
        weatherDataDTO.setCategory(category);
        weatherDataDTO.setFcstValue(category, fcstValue);
    weatherDataDTOList.add(weatherDataDTO);
}

- 사전에 만든 DTO에 for문을 사용해 데이터를 넣는다.

 

DTO

public void setCategory(String category){
    this.category = switch (category) {
        case "TMP" -> "1시간 기온";
        case "UUU" -> "풍속(동서성분)";
        case "VVV" -> "풍속(남북성분)";
        case "VEC" -> "풍향";
        case "WSD" -> "풍속";
        case "SKY" -> "하늘상태";
        case "PTY" -> "강수형태";
        case "POP" -> "강수확률";
        case "WAV" -> "파고";
        case "PCP" -> "1시간 강수량";
        default -> throw new IllegalStateException("Unexpected value: " + category);
    };
};

public void setFcstValue(String category, String fcstValue){
    this.fcstValue = switch (category) {
        case "TMP" -> fcstValue + "℃";
        case "UUU", "WSD", "VVV" -> fcstValue + "m/s";
        case "VEC" -> {
                        double value = Double.parseDouble(fcstValue);
                        double convertedValue = (value + 22.5 * 0.5) / 22.5;
                        int directionIndex = (int) Math.floor(convertedValue);
                        String[] directions = {"N", "NNE", "NE", "ENE", "E", "ESE", "SE", "SSE", "S", "SSW", "SW", "WSW", "W", "WNW", "NW", "NNW", "N"};
                        yield directions[directionIndex];
                      }
        case "SKY" -> {
                        int value = Integer.parseInt(fcstValue);
                        yield switch (value){
                            case 1, 2, 3, 4, 5 -> "맑음";
                            case 6, 7, 8 -> "구름많음";
                            case 9, 10 -> "흐림";
                            default -> throw new IllegalStateException("Unexpected value: " + value);
                        };
                      }
        case "PTY" -> { if (fcstValue.equals("0")) {
                            yield "강수없음";
                        } else {
                            yield fcstValue;
                        }}
        case "POP" -> fcstValue + "%";
        case "WAV" -> fcstValue + "M";
        case "PCP" -> { if (fcstValue.equals("강수없음")) {
                            yield fcstValue;
                        } else {
                            yield fcstValue + "mm";
                        }}
        default -> throw new IllegalStateException("Unexpected value: " + category);
    };

- 데이터를 받아보면 그냥 봐서는 이해할 수 없는 데이터가 출력된다.

- 항목은 일반인이 알아볼 수 있게 바꾸고, 데이터값 역시 적절히 조정한다

- 어차피 이 데이터를 쓸 때는 변형시켜 사용시키는 것이 좋다고 생각하기 때문에 DTO에서 바로 변경할 수 있도록 구현했다.

- 다만 이렇게 하는게 잘 하는 건지는 모르겠다;;

 

jsp

이것 저것 변경점이 많았지만 결국에는 지역만 설정하도록 결정했다.

이제부터 무슨 옷을 입을지를 고려하기 위해서는 지금 또는 지금 이후의 날씨 데이터가 중요하다.

그런데 지금 이후의 날씨 데이터는 안 불어와지니 가장 최신 날씨 데이터만 있으면 된다고 생각한다.

<form id="weatherRequest" action="<c:url value='/weatherRequest'/>" method="post">
    <input type="hidden" id="base_date" name="base_date">
    <input type="hidden" id="base_time" name="base_time">
    지역(경기도 한정):
    <select id="area" name="area">
        <option value="수원시장안구">수원시장안구</option>
        <option value="수원시권선구">수원시권선구</option>
        <option value="수원시팔달구">수원시팔달구</option>
        <option value="수원시영통구">수원시영통구</option>
        <option value="성남시수정구">성남시수정구</option>
        <option value="성남시중원구">성남시중원구</option>
        <option value="성남시분당구">성남시분당구</option>
        <option value="의정부시">의정부시</option>
        <option value="안양시만안구">안양시만안구</option>
        <option value="안양시동안구">안양시동안구</option>
        <option value="부천시원미구">부천시원미구</option>
        <option value="부천시소사구">부천시소사구</option>
        <option value="부천시오정구">부천시오정구</option>
        <option value="광명시">광명시</option>
        <option value="평택시">평택시</option>
        <option value="동두천시">동두천시</option>
        <option value="안산시상록구">안산시상록구</option>
        <option value="안산시단원구">안산시단원구</option>
        <option value="고양시덕양구">고양시덕양구</option>
        <option value="고양시일산동구">고양시일산동구</option>
        <option value="고양시일산서구">고양시일산서구</option>
        <option value="과천시">과천시</option>
        <option value="구리시">구리시</option>
        <option value="남양주시">남양주시</option>
        <option value="오산시">오산시</option>
        <option value="시흥시">시흥시</option>
        <option value="군포시">군포시</option>
        <option value="의왕시">의왕시</option>
        <option value="하남시">하남시</option>
        <option value="용인시처인구">용인시처인구</option>
        <option value="용인시기흥구">용인시기흥구</option>
        <option value="용인시수지구">용인시수지구</option>
        <option value="파주시">파주시</option>
        <option value="이천시">이천시</option>
        <option value="안성시">안성시</option>
        <option value="김포시">김포시</option>
        <option value="화성시">화성시</option>
        <option value="광주시">광주시</option>
        <option value="양주시">양주시</option>
        <option value="포천시">포천시</option>
        <option value="여주시">여주시</option>
        <option value="연천군">연천군</option>
        <option value="가평군">가평군</option>
        <option value="양평군">양평군</option>
    </select>
    <input type="submit" value="확인">
</form>

- 이 select 항목은 좀 문제가 있다

- 44개나 되는데 다른 방법을 생각해봐야 할 것 같다

- 만약 다른 지역(서울 등)까지 데이터를 제공하기 시작하면 country를 고르고, area를 고르는 방식이 될텐데 이 부분도 좀 귀찮은 작업이 될 것 같다.

 

js

// 오늘 날짜 데이터 전송
$("#weatherRequest").on("submit", function (){
    var base_date = $("#base_date");
    var base_time = $("#base_time");

    /** 현재 시간 및 날짜 설정 */
    // 오늘 날짜 및 시간 설정
    const today = new Date();
    const year = today.getFullYear();
    const month = (today.getMonth()+1).toString().padStart(2, "0");
    const day = today.getDate().toString().padStart(2, "0");
    const formattedDate = year+month+day;
    const hour = today.getHours()
    const now = today.toLocaleTimeString();

    // 오늘 날짜 및 시간 출력
    // console.log("오늘 날짜: " + formattedDate);
    // console.log("현재 시간: " + now);

    /** 최신 날짜 및 시간 지정 */
    let dateForAPI = formattedDate;
    let timeForAPI;

    switch (hour){
        case 0:
        case 1:
            const yesterday = new Date(today);
            yesterday.setDate(yesterday.getDate()-1);

            const yesterday_year = yesterday.getFullYear();
            const yesterday_month = (yesterday.getMonth()+1).toString().padStart(2, "0");
            const yesterday_day = yesterday.getDate().toString().padStart(2, "0");
            dateForAPI = yesterday_year+yesterday_month+yesterday_day;
            timeForAPI = "2300";
            break;
        case 2:
        case 3:
        case 4:
            timeForAPI = "0200";
            break;
        case 5:
        case 6:
        case 7:
            timeForAPI = "0500";
            break;
        case 8:
        case 9:
        case 10:
            timeForAPI = "0800";
            break;
        case 11:
        case 12:
        case 13:
            timeForAPI = "1100";
            break;
        case 14:
        case 15:
        case 16:
            timeForAPI = "1400";
            break;
        case 17:
        case 18:
        case 19:
            timeForAPI = "1700";
            break;
        case 20:
        case 21:
        case 22:
            timeForAPI = "2000";
            break;
        default:
            timeForAPI = "2300";
    }

    // 최신 날짜와 시간 출력
    // console.log("최신 날짜: " + dateForAPI);
    // console.log("최신 시간: " + timeForAPI);

    // 최신 날짜와 시간 전송
    base_date.val(dateForAPI);
    base_time.val(timeForAPI);
})

 

자동으로 날짜와 시간을 지정해주는 js코드이다.

- 현재 단기예보는 2시, 5시, 8시, 11시, 14시, 17시, 20시, 23시 데이터만 제공한다.

--> 가장 최신 데이터는 현재 시간을 기준으로 가장 근접한 시간으로 골라야 한다.

--> 그런데 오전 2시 이전이면 전날 날짜의 23시 데이터를 골라야 한다.

--> 복잡한 코드;;;

 

게다가 날짜와 시간은 20240511 / 0500 과 같은 형태로 입력되어야 하기 때문에 이리저리 제약이 많았다.

코드를 단순화하고 싶은데 엄두가 나질 않는다.

 

출력

오늘은 날씨가 맑고 바람이 별로 불지 않는 날이다

 

개선점

1. WeatherDataDTO의 Setter를 통한 데이터 변경의 타당성 검증(리소스 과다사용 여부 등)

2. 폼 데이터에 사용되는 데이터를 include 등의 작업을 통해 가시성 향상

3. country를 고르고 area를 고르는 방식으로 변경(확장성 고려)

4. 자바 코드 이해력 부족(Date함수, JSON데이터, BufferReader, Switch문, url 구성 등...)

 

프로그램 만들고 기능 구현하는 건 너무 재밌는데 이걸 검색 안하고 만들 정도의 능력을 갖출 생각을 하니 너무 막막하다...여전히 쉬운 코드도 혼자서 제대로 못 만드는 것 같다...