OpenWeatherMap api 사용해서 네이버 날씨랑 비슷한 인터페이스 만들기 도전!
이번 프로젝트 진행하면서 페이지 마무리 하고 있었는데
그래도 명색이 여행지 추천 사이트인데 날씨 정보가 없는게 이상해서 찾아보다가
마침 같이 학원다니는 문배의 추천으로 날씨 api도 쓰기로 결정!
그래서 도전!! 목표는 네이버 날씨처럼 비슷한 UI 구성하기
Openapi를 자주 써봤기 때문에 쉬울줄 알았다.. canvas를 만나기 전까진..
많은 오류들을 만났고 해결했기 때문에 오류+해결방법으로 구성하겠습니다~
처음부터 삐걱대면서 시작!
오류1. API 호출
openweathermap api 불러올 때 부터 삐걱댔다.
문배의 꿀팁과 구글링을 통해
var url = https://api.openweathermap.org/data/2.5/weather?lat=${lat}&lon=${lon}&appid=${apiKey}&units=metric&lang=kr;
이런 식으로 api 호출을 한다는 것을 알았고 바로 적용시켜서 api를 호출해봤다.
lat = parseFloat('${hotplace.mapx}');
lon = parseFloat('${hotplace.mapy}');
// 위도와 경도 값이 제대로 들어오는지 확인하는 로그
console.log("위도:", lat);
console.log("경도:", lon);
// OpenWeatherMap API 키
var apiKey = 'api키';
// API 호출 URL 만들기
var url = https://api.openweathermap.org/data/2.5/weather?lat=${lat}&lon=${lon}&appid=${apiKey}&units=metric&lang=kr;
결과는 역시나 실패
그럼 값을 직접 전달하는 하드코딩으로 다시 시도 → 실패
근데 주소창에
https://api.openweathermap.org/data/2.5/weather?lat=${lat}&lon=${lon}&appid=${apiKey}&units=metric&lang=kr;
각 값들을 넣으면 Object로 잘 불러와졌다.. 문제가 뭘까?
해결1. ${ }를 문자열 연결식으로 바꾸기
자바스크립트에서 사용하는 템플릿 리터럴에서 변수 삽입 구문인 ${ }를 사용하지 않고
문자열 연결 방식으로 했더니 됐다..
var lat = parseFloat('${hotplace.mapy}'); // JSP에서 hotplace.mapy (위도) 값 받아오기
var lon = parseFloat('${hotplace.mapx}'); // JSP에서 hotplace.mapx (경도) 값 받아오기
var apiKey = 'api 키'; // 실제 API 키
var url = 'https://api.openweathermap.org/data/2.5/weather?lat=' + lat + '&lon=' + lon + '&appid=' + apiKey + '&units=metric&lang=kr';
엥,, JSP에서 가져오는 hotplace의 mapy mapx좌표를 parseFloat로 안전하게 처리하고
api랑 다 문자열 연결 방식인 + 를 쓰니까 됐다..
어찌저찌 api 호출은 성공!
오류2. 객체들이 JSP 페이지에 나타나지 않음.
가져온 Object들이 jsp 페이지에 렌더링 되지 않는 현상 발생..
li.innerHTML = `
<div class="temp">${temp}°</div>
<span class="dot" style="top: ${40 + topOffset}px;"></span>
<div class="icon"><img src="${customIconUrl}" alt="weather-icon"></div>
<div class="time">${dateTime.slice(11, 16)}</div>
<div class="date">${dateTime.slice(5, 10)}</div>
<div class="day">${getDayOfWeek(dateTime)}</div>
`;
이렇게 백틱으로 감싸서 innerHTML로 처리하려고 했는데 화면에 아무 값들이 안나왔다.
왠지 저번에 백틱으로 감싸서 안나왔던걸 문자열 연결 방식으로 해결했던 기억이 나서 한번 해봤다
해결2. 문자열 연결 방식으로 해결
li.innerHTML =
'<div class="temp">' + temp + '°</div>' +
'<span class="dot" style="top: ' + (40 + topOffset) + 'px;"></span>' +
'<div class="icon"><img src="' + customIconUrl + '" alt="weather-icon"></div>' +
'<div class="time">' + dateTime.slice(11, 16) + '</div>' +
'<div class="date">' + dateTime.slice(5, 10) + '</div>' +
'<div class="day">' + getDayOfWeek(dateTime) + '</div>';
역시.. 이렇게 하니까 JSP 페이지에 제대로 렌더링이 됐다.. 휴
이제 어찌저찌 값을 받아오면서 화면에 출력하고 있었다. 화면에 출력하는건 이제 어느정도 하니까
막히는거 없이 잘 하고 있다가 거의 마무리 됐을때 쯤 네이버 날씨를 다시 봤다.
네이버 날씨에서는 온도를 점과 선으로 표시해 사용자들에게 편리한 GUI를 구성하고 있었다.
오 이쁘다! 하고 바로 도전!!
(이게 날 힘들게 할 줄 몰랐지 그땐...)
오류2. 각 리스트에 만든 <span>의 dot 위치 찾기 실패
바로 오류 등장!!!!!
네이버 날씨를 개발자 도구로 뜯어보니까 각각의 날씨를 li로 구성해서 정보를 전달하고 있었다.
그래서 나도 똑같이 리스트 안에 flex-direction: column;으로 세로 정렬을 해서
api에서 가져온 각각의 값들을 가져오려고 했다.
// 리스트 항목 생성
var li = document.createElement('li');
li.innerHTML =
'<div class="temp">' + temp + '°</div>' +
'<span class="dot" style="top: ' + (40 + topOffset) + 'px;"></span>' + // 점의 위치 조정
'<div class="icon"><img src="' + customIconUrl + '" alt="weather-icon"></div>' +
'<div class="time">' + dateTime.slice(11, 16) + '</div>' +
'<div class="date">' + dateTime.slice(5, 10) + '</div>' +
'<div class="day">' + getDayOfWeek(dateTime) + '</div>'; // 요일 표시
// 리스트에 추가
document.querySelector('.temperature-list').appendChild(li);
dot 만드는거까진 성공! <span>으로 점을 찍어줬고, 첫번째 점을 기준온도로 해서 1도당 2px씩 +-값을 줬다.
// 온도 차이에 따라 점의 위치 계산 (1도당 2px 이동)
var tempDifference = temp - baseTemp; // 기준 온도와의 차이
var topOffset = -tempDifference * 2; // 1도당 2px 위로 이동 (기준 온도보다 높으면 위로, 낮으면 아래로)
이제 이걸 그냥 이으면 되니까 쉽네~~ 하고 있었다..
근데 각각의 <li> 형식이라 css의 after로 이어지지도 않았고 어떻게 처리할까 고민하면서 찾아보다가
html에 canvas 요소가 있다는걸 알았다!
그냥 canvas 깔아놓고 거기 위에 쓱쓱 그리면 되겠네~ 하고 시작!
<span>으로 만든 dot 위치를 찾아서 canvas에서 선을 이으면 된다.
// dot이 그려진 후, 좌표를 찾아 선을 그리는 작업 실행
setTimeout(function() {
var dots = document.querySelectorAll('.temperature-list .dot');
var points = [];
// 각 dot 요소의 위치를 계산
dots.forEach(function(dot, index) {
var rect = dot.getBoundingClientRect(); // 요소의 좌표 정보 얻기
//var xPos = rect.left + (rect.width / 2); // 중앙 x 좌표 계산
//var yPos = rect.top + (rect.height / 2); // 중앙 y 좌표 계산 (window.scrollY 제거)
var xPos = rect.x;
var yPos = rect.y;
// 좌표 저장
points.push({ x: xPos, y: yPos });
//points.push({ x: rect.x, y: rect.y });
//쌤이 알려주신 console.log
//console.log("x:"+rect.x+",y:"+rect.y);
//console.log(`Dot ${index + 1}: X = ${rect.x}, Y = ${rect.y}`);
});
// 점들을 이음
drawLines(points);
}, 0); // 1초 후에 좌표 계산 시작 (렌더링 후 지연 시간)
});
// 선 그리는 함수
function drawLines(points) {
var canvas = document.getElementById('weatherCanvas');
var ctx = canvas.getContext('2d');
//Canvas 크기, 위치 가져오기
var canvasRect = canvas.getBoundingClientRect(); // canvas의 화면 내 위치 가져오기
//캔버스 크기 console
//console.log("Canvas width: " + canvas.width + ", height: " + canvas.height);
ctx.clearRect(0, 0, canvas.width, canvas.height); // 기존 선을 지움
ctx.translate(0, 2); // 전체 Y 좌표를 1px 아래로 이동
// 선 그리기
if (points.length > 0) {
ctx.beginPath();
// 첫 번째 점으로 이동 (캔버스 좌표계로 변환 + 1px 아래로 이동)
ctx.moveTo(points[0].x - canvasRect.left, points[0].y - canvasRect.top);
// 각 점으로 선을 그림
for (var i = 1; i < points.length; i++) {
//어디서부터 선 그렸는지 console.log
//console.log(`Drawing line to Point ${i + 1}: X = ${points[i].x}, Y = ${points[i].y}`);
ctx.lineTo(points[i].x - canvasRect.left, points[i].y - canvasRect.top);
}
ctx.strokeStyle = '#D5D5D5'; // 선 색상
ctx.lineWidth = 2; //선 굵기
ctx.stroke();
}
}
};
결과는?
지금부터 대환장 '점' 파티 사진들 추가합니다..
이런거 말고도 캔버스 밑에 그려지고 막 어디 뚫고 나가고 난리였다..
뭔가 점의 값을 못 받아오는거 같아서 생각하느라 당 떨어지고 머리가 아팠는데 간식요정이 귤을 선물해 주고 가셨다!!!
덕분에 오전 버텼어요,, 무한감사,,🙇♂️🙇♂️
다시 멘탈 잡고!! 천천히 찾아봤는데 렌더링 이슈가 있었다.
해결2. 렌더링 시간으로 dot 위치 잡기!
.dot의 위치를 가져올 때 getBoundingClientRect()를 사용했는데 x,y값이 없거나 가져와도 이상했다.
이유는 브라우저에서 DOM이 완전히 렌더링 되기 전에 getBoundingClientRect()를 호출해서 좌표를 찾으려다 보니까 잘못된 값을 전달받고 이상하게 된 거였다.
그래서 setTimeout을 사용해서 렌더링이 끝나고 좌표를 가져오게 수정했고 dot의 x,y좌표를 잘 가져왔다!
처음에는 코드 문제인가 했는데 코드에는 별 이상이 없었고 좌표를 구하는 타이밍이 맞지 않아서 생긴 문제였다..
다음 해결해야 할 문제는 선 그리기..
문제3. dot 좌표 가져온 후 선으로 연결하기 실패
이제 dot 좌표를 가져왔으니 선을 그리면 되겠다! 싶었는데 선이 자꾸 잘못된 위치에 그려졌다.
콘솔에도 좌표 잘 찍히고 있는데 왜 선은 저렇게 그려지고 캔버스 하단에 그려지고 하는건지.. 도저히 모르겠어서
쌤한테도 여쭤보고 했지만 코드상엔 이상이 없었고 css나 다른 문제라는걸 알았다.
결국 쌤의 조언과 구글링+지피티와 씨름한 결과 canvas의 크기를 찾는것과 실제 화면에서 찾는것과 다르다는 것을 알았고
선을 그릴 때 Y좌표가 화면 좌표랑 캔버스의 좌표가 맞지 않아서 선이 자꾸 이상하게 그려진다는 것을 알았다.
해결3. 캔버스 좌표랑 화면 좌표 일치시키기
아주 잘못된 나쁜 코드 ↓
// 선 그리기
ctx.beginPath();
ctx.moveTo(points[0].x, points[0].y); // 첫 번째 점으로 이동
for (var i = 1; i < points.length; i++) {
ctx.lineTo(points[i].x, points[i].y); // 각 점을 선으로 연결
}
ctx.strokeStyle = '#ccc';
ctx.lineWidth = 2;
ctx.stroke();
ctx.moveTo()와 ctx.lineTo()에서 화면 좌표를 맞추지 않은 상태..
이러니 자꾸 canvas 아래에 그려지고 이상한곳에 선 그려지고 그러지... 휴
올바른 코드🤗
// 캔버스의 화면 내 위치 가져오기
var canvasRect = canvas.getBoundingClientRect();
// 선 그리기
ctx.beginPath();
ctx.moveTo(points[0].x - canvasRect.left, points[0].y - canvasRect.top); // 캔버스 좌표로 변환
for (var i = 1; i < points.length; i++) {
ctx.lineTo(points[i].x - canvasRect.left, points[i].y - canvasRect.top); // 좌표 변환 후 선 그리기
}
ctx.strokeStyle = '#ccc';
ctx.lineWidth = 2;
ctx.stroke();
캔버스 좌표로 변환!! 이게 가장 핵심이였다..
이렇게 캔버스 좌표로 맞춰주니까 결론은?!
드디어 됐다!!!!!ㅏㄹ ㄴㄹ이ㅜㅏㅓ능 ㄿ느 ㅐㅑㄷ쟈 ㅡㅁㄴ야르 ㅣㅏ느!!!
어느정도 스타일링도 하고 아이콘도 가져와서 OpenWeatherMap 에서 제공하는 날씨랑 매칭시켜놨다.
// 아이콘 커스텀
function getCustomIconUrl(iconCode) {
var iconMap = {
'01d': 'https://ssl.pstatic.net/sstatic/keypage/outside/scui/weather_new_new/img/weather_svg_v2/icon_flat_wt1.svg',
'01n': 'https://ssl.pstatic.net/sstatic/keypage/outside/scui/weather_new_new/img/weather_svg_v2/icon_flat_wt2.svg',
'02d': 'https://ssl.pstatic.net/sstatic/keypage/outside/scui/weather_new_new/img/weather_svg_v2/icon_flat_wt3.svg',
'02n': 'https://ssl.pstatic.net/sstatic/keypage/outside/scui/weather_new_new/img/weather_svg_v2/icon_flat_wt6.svg',
'03d': 'https://ssl.pstatic.net/sstatic/keypage/outside/scui/weather_new_new/img/weather_svg_v2/icon_flat_wt5.svg',
'03n': 'https://ssl.pstatic.net/sstatic/keypage/outside/scui/weather_new_new/img/weather_svg_v2/icon_flat_wt6.svg',
'04d': 'https://ssl.pstatic.net/sstatic/keypage/outside/scui/weather_new_new/img/weather_svg_v2/icon_flat_wt7.svg',
'04n': 'https://ssl.pstatic.net/sstatic/keypage/outside/scui/weather_new_new/img/weather_svg_v2/icon_flat_wt6.svg',
'09d': 'https://ssl.pstatic.net/sstatic/keypage/outside/scui/weather_new_new/img/weather_svg_v2/icon_flat_wt9.svg',
'10d': 'https://ssl.pstatic.net/sstatic/keypage/outside/scui/weather_new_new/img/weather_svg_v2/icon_flat_wt22.svg',
'11d': 'https://ssl.pstatic.net/sstatic/keypage/outside/scui/weather_new_new/img/weather_svg_v2/icon_flat_wt18.svg',
'13d': 'https://ssl.pstatic.net/sstatic/keypage/outside/scui/weather_new_new/img/weather_svg_v2/icon_flat_wt11.svg',
'50d': 'https://ssl.pstatic.net/sstatic/keypage/outside/scui/weather_new_new/img/weather_svg_v2/icon_flat_wt17.svg'
};
return iconMap[iconCode] || 'https://example.com/icons/default-icon.png';
}
이제 다른 페이지들 디테일 잡을 수 있다!!! 하루를 꼬박 이거에 매달렸더니 피곤...
캔버스 너 도대체 뭔데?
캔버스를 사용하는 이유
<canvas>는 비트맵 기반 그래픽을 그릴 때 유용하며, 특히 동적 그래픽, 애니메이션, 실시간 데이터 시각화 등에 주로 사용됩니다. canvas의 장점과 사용 용도는 다음과 같습니다:
- 장점:
- 고성능: 캔버스는 비트맵 방식으로 동작하므로 픽셀 단위로 정교한 그래픽을 구현할 수 있습니다. 특히 실시간 데이터 시각화나 애니메이션을 구현할 때 매우 유리합니다.
- 유연성: 다양한 그래픽 요소를 동적으로 그리거나 수정할 수 있습니다.
- 브라우저 내장: 별도의 플러그인 없이 HTML5와 함께 브라우저에서 기본적으로 사용할 수 있습니다.
- 사용 용도:
- 데이터 시각화: 그래프, 차트, 실시간 데이터 시각화 등에 자주 사용됩니다.
- 게임 개발: 브라우저에서 동작하는 게임을 만들 때 주로 사용됩니다.
- 애니메이션: 프레임 기반 애니메이션을 구현하는 데 적합합니다.
- 이미지 처리: 이미지 필터링, 수정, 합성 등을 수행할 수 있습니다.
canvas는 비트맵 방식으로 매우 세밀하게 제어할 수 있어, 선 그리기와 같은 복잡한 그래픽 작업을 다룰 때 적합한 도구입니다.
라네요 지피티님이..
구글링해서 더 찾아보니까
웹 페이지에서 그래픽을 그리기 위한 HTML 요소고, javascript 써서 동적으로 그래픽 생성하고 조작할 수 있는 강력한 도구고, 다양한 웹 기반 그래픽 애플리케이션 만들 수 있고, 2D 그래픽, 애니메이션, 인터렉션, 차트 및 그래프, 게임 개발, 이미지 처리, 그래픽 편집기, 그림판 등등 다양하게 쓰인다고 합니다?
저는 저렇게 웅장한 기술들중에 '차트 및 그래프' 때문에 써봤는데.. 처음 써보는 거라 많이 어려웠다는 후기..
하루를 꼬박 썼는데 그래도 해결해서 너무 뿌듯한 상태로 마무리!! 끝!!! 이제 기능 추가 더 안하기로 다짐하기! 땅땅땅!
🙇♂️ 감사인사 전하기 🙇♂️
(귤과 바나나로 제 멘탈을 지켜주신 간식요정님,
꿀팁으로 새로운 공부를 시켜준 문배친구님,
코드 쭉 봐주시면서 같이 고민해주신 쌤..
모두들 감사해요.. 덕분에 해결하고 맨정신으로 돌아왔어요..)
'Develop > JSP' 카테고리의 다른 글
공공데이터 API - 서울 맛집 API로 DB저장 구현하기 (2) (3) | 2024.10.27 |
---|---|
공공데이터 API - 서울 맛집 API로 DB저장 구현하기 (1) (2) | 2024.10.27 |
구글 지도 API 써서 일정 페이지 만들기 (4) | 2024.10.22 |
메인 페이지 정리, 페이지 하나로 DB 총 관리하기 (1) (3) | 2024.10.14 |
핫플 페이지 마무리 겸 정리하기 (2) (8) | 2024.10.06 |