★https://this-circle-jeong.tistory.com/244
지도 : 카카오 API
날씨 : 기상청 공공 포탈 데이터
장소를 검색하면 마커로 위치가 나오고 옆의 그 날씨를 표시한다
<%@ page contentType="text/html; charset=UTF-8" language="java"%>
<!DOCTYPE html>
<html lang="ko">
<head>
<meta charset="UTF-8">
<title>Index</title>
<link rel="stylesheet" href="css/style.css">
</head>
<body>
<div id="wrapper">
<header>
<img src="img/weather.png" alt="날씨 아이콘">
<p>날씨 단기예보 검색</p>
</header>
<main>
<div class="ct_msg">
<p>장소를 검색하세요</p>
</div>
<form action="search.jsp" method="GET"> //serch페이지 겟방식으로 이동
<div class="search_bar">
<img src="img/search.png" alt="검색 아이콘">
<input class="searchText" name="location" type="text"
placeholder="장소를 입력하세요">
<button class="btnSearch" type="submit">검색</button>
</div>
</form>
</main>
<footer>
<div class="ct_footer">Copyright© jjh0807a All RIGHTS RESERVED.</div>
</footer>
</div>
</body>
</html>
<%@ page language="java" contentType="text/html; charset=UTF-8"
pageEncoding="UTF-8"%>
<%
String location = request.getParameter("location");//사용자가 입력한 지역명 가져오기
%>
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>검색</title>
<link rel="stylesheet" href="css/style.css">
<!-- 카카오 지도 API -->
<script type="text/javascript"
src="<https://dapi.kakao.com/v2/maps/sdk.js?appkey=**발급받은키**services>"></script>
<script>
window.onload = function () { //메인 시작, 페이지 로딩이 완료되면 실행
var container = document.querySelector('.ct_map'); // 지도 담을 컨테이너
var options = { //지도초기설정
center: new kakao.maps.LatLng(37.5665, 126.9780), // 서울시청 (기본 위치)
level: 3 //확대할 사이즈
};
var map = new kakao.maps.Map(container, options); // 지도 생성
//url에서 location 파라미터 추출
var locationParam = new URLSearchParams(window.location.search).get('location'); // url에 location이라는 이름의 검색어 포함
// 검색어가 입력되었는지 확인
if (locationParam) { //만약 사용자가 검색한 location이 값이 있을 때,
var geocoder = new kakao.maps.services.Geocoder(); // 주소-좌표 변환 객체 생성
//입력된 주소명으로 좌표 검색
geocoder.addressSearch(locationParam, function(result, status) {
if (status === kakao.maps.services.Status.OK) { // 정상적으로 검색되었을 경우
console.log("정상적으로 검색");
var coords = new kakao.maps.LatLng(result[0].y, result[0].x); // 검색된 위도, 경도 추출
var lat = coords.getLat();//위도
var lon = coords.getLng();//경도
var marker = new kakao.maps.Marker({ // 마커 생성(location표시)//let으로도 가능
map: map, //자바스크립트의 객체리터럴 : 코드 상에서 직접 객체를 정의할 때 쓰는 문법
position: coords
});
//인포윈도우 생성
var infowindow = new kakao.maps.InfoWindow({ // 인포윈도우 표시
content: '<div style="width:150px;text-align:center;padding:6px 0;">' + locationParam + '</div>'
});
infowindow.open(map, marker);
map.setCenter(coords); // 지도 중심 이동
console.log(coords);//la,ma (coords=LatLng 객체하나)
console.log(typeof coords);//object
fetchWeather(lat, lon, locationParam); // 날씨 정보 가져오기
} else {
console.log("정상적인 검색이 아님, 기본 위치(서울시청)로 이동");
// 기본 위치 (서울시청) 좌표로 이동
var defaultCoords = new kakao.maps.LatLng(37.5665, 126.9780); // 서울시청
// 마커생성 : 장소가 나오지 않을 때
var marker = new kakao.maps.Marker({
map: map,
position: defaultCoords
});
map.setCenter(defaultCoords); // 지도 중심 서울시청으로
fetchWeather(37.5665, 126.9780, "서울"); // 기본 위치 서울의 날씨 정보
}
}); // 지오코더 끝
} // if (locationParam) 끝
}; // 메인 끝
function getWindDirection(degree) { //풍향>>문자 (degree=각도, 0~360도)
const dirs = ["북", "북동", "동", "남동", "남", "남서", "서", "북서"];
const ix = Math.round(degree / 45) % 8; //360도원을 45도씩 나눈 8개 구간,%8은 인덱스가 8이상 되는 걸 방지
return dirs[ix] + "풍"; //나온 문자열에 "풍"을 붙여서 return;
}
// 날씨 정보 가져오는 함수
function fetchWeather(lat, lon, location) {
fetch("weatherapi.jsp?lat=" + lat + "&lon=" + lon + "&location=" + encodeURIComponent(location))
.then(res => res.json()) //화살표함수, function 키워드를 쓰지 않고 (매개변수)=>{함수내용} 형태로 간단하게 함수를 정의하는 문법
.then(data => {
const skyMap = { "1": "☀️ 맑음", "3": "⛅ 구름많이", "4": "☁️ 흐림" };
const ptyMap = { "0": "🌤️ 구름조금", "1": "🌧️ 비", "2": "🌨️ 비/눈", "3": "❄️ 눈", "4": "🌦️ 소나기" };
//fetch("어디에 요청해")
// .then(응답이 오면 그걸 JSON으로 바꿔)
// .then(JSON이 오면 그걸로 화면에 날씨 출력해)
// 문자열을 + 로 연결해서 출력 ★★★
const html =
'<div class="weather-wrapper">' +
'<div class="weather-header">' +
'<div class="weather-temp">' + data.temp + '°</div>' +
'<div class="weather-condition">' + (skyMap[data.sky] || "") + ' ' + (ptyMap[data.pty] || "") + '</div>' +
'</div>' +
'<div class="weather-cards">' +
'<div class="weather-card rain">' +
'<div class="label">강수</div>' +
'<div class="value">' + (data.pty === "0" ? "없음" : ptyMap[data.pty]) + '</div>' +
'</div>' +
'<div class="weather-card humidity">' +
'<div class="label">습도</div>' +
'<div class="value">' + data.reh + '%</div>' +
'</div>' +
'<div class="weather-card wind">' +
'<div class="label">바람</div>' +
'<div class="value">' + getWindDirection(data.vec) + ' ' + data.wsd + 'm/s</div>' +
'</div>' +
'</div>';
// .ct_weather에 HTML 삽입
document.querySelector(".ct_weather").innerHTML = html;
})
.catch(err => {
console.error("날씨 API 오류", err);
document.querySelector(".ct_weather").innerHTML = "날씨 정보를 불러오는 데 실패했습니다.";
});
}
</script>
</head>
<body>
<div id="wrapper">
<header>
<img src="img/weather.png" alt="날씨 아이콘">
<p>날씨 단기예보 검색</p>
</header>
<main>
<div class="ct_search">
<form action="search.jsp" method="get">
<div class="search_bar">
<img src="img/search.png" alt="검색 아이콘"> <input
class="searchText" type="text" name="location"
placeholder="행정구역(시.도/시.군.구/읍.면.동)으로 검색">
<button type="submit" class="btnSearch">검색</button>
</div>
</form>
</div>
<div class="ly_content">
<div class="ct_map">지도</div>
<div class="ct_weather">날씨</div>
</div>
</main>
<footer>
<div class="ct_footer">Copyright© jjh0807a Rights RESERVED.</div>
</footer>
</div>
</body>
</html>
<%@ page contentType="application/json; charset=UTF-8" %>
<%@ page import="java.net.*, java.io.*, org.json.simple.*, org.json.simple.parser.*,java.text.SimpleDateFormat" %>
<%!
// 위경도 → 격자 변환용 클래스 & 함수
class LatXLngY {
public double lat;
public double lng;
public int x;
public int y;
}
public LatXLngY convertGRID(double lat, double lng) {
double RE = 6371.00877; // 지구 반경(km)
double GRID = 5.0; // 격자 간격(km)
double SLAT1 = 30.0; // 투영 위도1(degree)
double SLAT2 = 60.0; // 투영 위도2(degree)
double OLON = 126.0; // 기준점 경도
double OLAT = 38.0; // 기준점 위도
double XO = 43; // 기준점 X좌표(GRID)
double YO = 136; // 기준점 Y좌표(GRID)
double DEGRAD = Math.PI / 180.0;
double re = RE / GRID;
double slat1 = SLAT1 * DEGRAD;
double slat2 = SLAT2 * DEGRAD;
double olon = OLON * DEGRAD;
double olat = OLAT * DEGRAD;
double sn = Math.tan(Math.PI * 0.25 + slat2 * 0.5) / Math.tan(Math.PI * 0.25 + slat1 * 0.5);
sn = Math.log(Math.cos(slat1) / Math.cos(slat2)) / Math.log(sn);
double sf = Math.tan(Math.PI * 0.25 + slat1 * 0.5);
sf = Math.pow(sf, sn) * Math.cos(slat1) / sn;
double ro = Math.tan(Math.PI * 0.25 + olat * 0.5);
ro = re * sf / Math.pow(ro, sn);
LatXLngY rs = new LatXLngY();
rs.lat = lat;
rs.lng = lng;
double ra = Math.tan(Math.PI * 0.25 + (lat) * DEGRAD * 0.5);
ra = re * sf / Math.pow(ra, sn);
double theta = lng * DEGRAD - olon;
if (theta > Math.PI) theta -= 2.0 * Math.PI;
if (theta < -Math.PI) theta += 2.0 * Math.PI;
theta *= sn;
rs.x = (int)(Math.floor(ra * Math.sin(theta) + XO + 0.5));
rs.y = (int)(Math.floor(ro - ra * Math.cos(theta) + YO + 0.5));
return rs;
}
%>
<%
try { //search.jsp에서 fetch로 전달한 위도/경도
String lat = request.getParameter("lat");//latitude
String lon = request.getParameter("lon");
String location = request.getParameter("location");
// 1. 위도/경도를 격자(x, y)로 변환 (예시 고정값 또는 추후 계산)
double latVal = Double.parseDouble(lat);
double lonVal = Double.parseDouble(lon);
LatXLngY grid = convertGRID(latVal, lonVal);
int nx = grid.x;
int ny = grid.y;
// 2. 날짜/시간 계산
java.util.Calendar cal = java.util.Calendar.getInstance();
int minute = cal.get(java.util.Calendar.MINUTE);
if (minute < 30) {
cal.add(java.util.Calendar.HOUR_OF_DAY, -1);
minute += 30;
} else {
minute -= 30;
}
java.text.SimpleDateFormat dateFormat = new java.text.SimpleDateFormat("yyyyMMdd");
String base_date = dateFormat.format(cal.getTime());
String base_time = String.format("%02d%02d", cal.get(java.util.Calendar.HOUR_OF_DAY), minute);
// 3. 기상청 API 호출
String serviceKey = "**발급받은키**";
String apiUrl = "<https://apis.data.go.kr/1360000/VilageFcstInfoService_2.0/getUltraSrtFcst>"
+ "?serviceKey=" + serviceKey
+ "&pageNo=1&numOfRows=1000&dataType=JSON"
+ "&base_date=" + base_date
+ "&base_time=" + base_time
+ "&nx=" + nx + "&ny=" + ny;
URL url = new URL(apiUrl);
HttpURLConnection conn = (HttpURLConnection) url.openConnection();
conn.setRequestMethod("GET");
BufferedReader br = new BufferedReader(new InputStreamReader(conn.getInputStream(), "UTF-8"));
StringBuilder sb = new StringBuilder();
String line;
while ((line = br.readLine()) != null) sb.append(line);
br.close();
// 4. JSON 파싱
JSONParser parser = new JSONParser();
JSONObject responseObj = (JSONObject) parser.parse(sb.toString());
JSONObject body = (JSONObject) ((JSONObject) responseObj.get("response")).get("body");
JSONObject items = (JSONObject) body.get("items");
JSONArray itemArr = (JSONArray) items.get("item");
String temp = "", sky = "", reh = "", wsd = "", pty = "", vec = "";
for (Object obj : itemArr) {
JSONObject item = (JSONObject) obj;
String category = (String) item.get("category");
String value = (String) item.get("fcstValue");
if (category.equals("T1H")) {//기온
temp = value;
} else if (category.equals("SKY")) {//하늘상태
sky = value;
} else if (category.equals("REH")) {//강수형태
reh = value;
} else if (category.equals("WSD")) {//습도
wsd = value;
} else if (category.equals("PTY")) {//풍속
pty = value;
} else if (category.equals("VEC")) {//풍향
vec = value;
}
}
//json파싱
//기상청에서 준 데이터는 문자열
JSONObject result = new JSONObject();
result.put("temp", temp);
System.out.println("temp= "+temp);
result.put("sky", sky);
System.out.println("sky= "+sky);
result.put("reh", reh);
result.put("wsd", wsd);
result.put("pty", pty);
result.put("vec", vec);
out.print(result.toJSONString());
} catch (Exception e) {
e.printStackTrace();
JSONObject error = new JSONObject();
error.put("error", "데이터 처리 중 오류 발생");
out.print(error.toJSONString());
}
%>