본문 바로가기
Develop/JPA

전기차 충전소 API 데이터 가공하기 (3)

by ys2ys2 2024. 11. 11.

오늘 작업한 내용들 정리하기!

 

✅충전소 검색 결과 CSS

✅페이지네이션 버튼 누르면 검색 결과의 최상단으로 이동

✅각 충전기 상태 업데이트

✅이용자 제한 여부 표시

✅사용자한테 현재 위치 받아서 카카오지도에 표시하기

✅검색결과에 나온 충전소 위치들의 lat,lng(위도,경도) 이용해서 카카오지도에 전달해서 마커표시

✅입력 폼 삭제

✅충전기 타입에 대한 필터링 부분 적용하기

 


 

 

✅충전소 검색 결과 CSS

 

 

기존에 이렇게 나오는 부분을 정리했다.

 

충전소 검색부분 CSS

 

 

 


 

 

✅페이지네이션 / 버튼 누르면 검색 결과의 최상단으로 이동

 

 

결과값에 대한 페이지네이션을 주고 좌 우 버튼을 통해 10개씩 표시되게 했다.

한 결과값에 대한 list는 30개씩 가져오게 했다! 스크롤 해서 30개의 충전소 리스트를 확인할 수 있다.

// 페이지네이션
function setupPagination() {
    const paginationContainer = document.getElementById("pagination");
    paginationContainer.innerHTML = "";

    //첫 번째와 마지막 페이지 번호 계산
    const startPage = (currentButtonGroup - 1) * buttonsPerPage + 1;
    const endPage = Math.min(currentButtonGroup * buttonsPerPage, totalPage);

    // 이전 버튼
    if (currentButtonGroup > 1) {
        const prevButton = document.createElement("button");
        prevButton.textContent = "◀";
        prevButton.className = "page-button";
        prevButton.addEventListener("click", function () {
            currentButtonGroup--; // 이전 그룹 이동
            setupPagination(); // 페이지네이션 버튼 재설정
        });
        paginationContainer.appendChild(prevButton);
    }

    // 페이지 번호 버튼
    for (let i = startPage; i <= endPage; i++) {
        const pageButton = document.createElement("button");
        pageButton.textContent = i;
        pageButton.className = "page-button";
        if (i === currentPage) pageButton.classList.add("active");

        pageButton.addEventListener("click", function () {
            currentPage = i; // 선택한 페이지로 업데이트
            filterAndDisplayResults(); // 페이지 변경 시 필터링된 결과 표시
        });

        paginationContainer.appendChild(pageButton);
    }

    // 다음 버튼
    if (endPage < totalPage) {
        const nextButton = document.createElement("button");
        nextButton.textContent = "▶";
        nextButton.className = "page-button";
        nextButton.addEventListener("click", function () {
            currentButtonGroup++; // 다음 그룹 이동
            setupPagination();
        });
        paginationContainer.appendChild(nextButton);
    }

}

 

 

사용자 편의성을 위해 페이지네이션 클릭 시 해당 div의 상단으로 이동되게 했다.

// 검색 결과 리스트 스크롤 상단 이동 추가
const searchListDiv = document.querySelector(".searchlist");
searchListDiv.scrollTop = 0;

 

 

 


 

 

✅각 충전기 상태 업데이트

 

사용자들이 충전기에 대한 상태를 실시간으로 확인할 수 있게 업데이트 했다.

사용하고 있는 API에서 제공하는 stat 값을 사용했다.

 

충전기 상태 값

 

 

각각의 충전기 상태에 해당하는 값들을 API 호출 시에 불러오고

  for (let i = 0; i < items.length; i++) {
	        const statNm = items[i].getElementsByTagName("statNm")[0].textContent;	
	        const addr = items[i].getElementsByTagName("addr")[0].textContent;		
	        const useTime = items[i].getElementsByTagName("useTime")[0].textContent;
	        const output = items[i].getElementsByTagName("output")[0].textContent;	
	        const type = items[i].getElementsByTagName("chgerType")[0].textContent;
            	const stat = items[i].getElementsByTagName("stat")[0].textContent;	//충전기 상태

 

충전기 상태도 추가하면서 각각의 span값에 class를 줘서 스타일링을 했다.

// 충전기 상태
const chargerStatusMap = {
    "1": { text: "사용 불가", class: "status-unavailable" },
    "2": { text: "사용 가능", class: "status-available" },
    "3": { text: "사용 중", class: "status-in-use" },
    "4": { text: "운영 중지", class: "status-out-of-service" },
    "5": { text: "점검 중", class: "status-under-maintenance" },
    "9": { text: "상태 미확인", class: "status-unknown" }
};

 

각각 class에 대한 css

/* 충전기 상태 */
.stat {
    background-color: #007bff;
	color:white;
}

.status-available {
    background-color: #4CC417; /* 사용 가능 */
    color:white;
}

.status-in-use {
    background-color: #F62217; /* 사용 중 */
    color:white;
}

.status-unavailable {
    background-color: #F62217; /* 사용 불가 */
    color:white;
}

.status-out-of-service {
    background-color: #6c757d; /* 운영 중지 */
    color:white;
}

.status-under-maintenance {
    background-color: #FBB117; /* 점검 중 */
    color:white;
}

.status-unknown {
    background-color: #343a40; /* 상태 미확인 */
    color:white;
}

 

사용 중, 사용가능, 상태 미확인 등등 각각의 상태에 대해 색상을 줘서 사용자들이 알아보기 쉽게 했다.

 

한계 : API 호출해서 받아오는 값들을 사용자들한테 제공하는 로직이라. API호출값이 잘못되면 잘못된 정보를 제공할 수 있다.

 

 


 

 

✅이용자 제한 여부 표시

 

같은 방식으로 이용자 제한 여부도 표시했다.

 

 

 

이용자 제한은 limitYn과 Y/N으로 제공하고 있었다.

이용제한 사유도 주고 있어서 활용!

이용자 제한은 Y값이 있으면 이용제한 사유가 나오게 했다.

for (let i = 0; i < items.length; i++) {
    const statNm = items[i].getElementsByTagName("statNm")[0].textContent;	
    const addr = items[i].getElementsByTagName("addr")[0].textContent;		
    const useTime = items[i].getElementsByTagName("useTime")[0].textContent;
    const output = items[i].getElementsByTagName("output")[0].textContent;	
    const type = items[i].getElementsByTagName("chgerType")[0].textContent;	
    const stat = items[i].getElementsByTagName("stat")[0].textContent;		
    const limitYn = items[i].getElementsByTagName("limitYn")[0]?.textContent || "N"; // limitYn 값 또는 기본값 'N'
    const limitDetail = items[i].getElementsByTagName("limitDetail")[0]?.textContent || ""; // limitDetail 값 또는 기본값 ''
    
	        
    // 이용 제한 정보 설정
    const limitText = limitDetail ? limitDetail : "제한 없음";
    
    
    listItem.innerHTML = `
    <div class="info">
        <h3>${displayName}</h3>
        <p>${addr}</p>
        <div class="status">
            <span class="stat ${statusInfo.class}">${statusInfo.text}</span>
            <span class="fast">⚡ ${output}kW</span>
            <span class="type">타입: ${typeText}</span>
        </div>
        <p>이용시간: ${useTime}</p>
        <p>이용자 제한사항: ${limitText}</p>
    </div>
    `;

 

 

 


 

 

 

✅사용자한테 현재 위치 받아서 카카오지도에 표시하기

 

 

사용자의 동의를 받고 현재 위치를 받은 후 그 값으로 지도를 설정하려고 했다.

검색해 보니까 이런 기능을 사용하려면 GeolocationApi를 사용해야 한다고 했다.

 

참고사이트 : https://developer.mozilla.org/ko/docs/Web/API/Geolocation_API/Using_the_Geolocation_API

// 현재 위치를 받아 지도의 중심을 이동하는 함수
function setMapToCurrentLocation() {
    if (navigator.geolocation) {
        navigator.geolocation.getCurrentPosition(
            function(position) {
                const latitude = position.coords.latitude; // 위도
                const longitude = position.coords.longitude; // 경도
                
                const userLocation = new kakao.maps.LatLng(latitude, longitude); // 사용자의 현재 위치 좌표
                map.setCenter(userLocation); // 지도의 중심을 현재 위치로 이동
                
                // 현재 위치에 마커 추가 (옵션)
                const userMarker = new kakao.maps.Marker({
                    position: userLocation,
                    map: map
                });
                
            },
            function(error) {
                alert("현재 위치를 가져올 수 없습니다. 위치 정보를 허용해 주세요.");
            },
            {
                enableHighAccuracy: true, // 높은 정확도의 위치를 요청
                timeout: 10000,           // 위치 요청 제한 시간 설정 (10초)
                maximumAge: 0             // 항상 최신 위치 정보 요청
            }
        );
    } else {
        alert("이 브라우저에서는 현재 위치를 가져올 수 없습니다.");
    }
}

// 페이지 로드 시 현재 위치로 지도 이동
document.addEventListener("DOMContentLoaded", setMapToCurrentLocation);

 

 

근데 이렇게 하고나니 카카오에서도 같은 기능을 제공하고 있었다. 일단 지금 쓴 코드로 쓰고 어떤게 더 정확한지 비교해서 바꿀 예정!

참고사이트 : https://apis.map.kakao.com/web/sample/geolocationMarker/

 

위치정보 받기

 

 

최초 페이지 접속 시 위치정보를 받는다. 허용을 하게 되면

 

위치정보 제공

 

이렇게 해당 위치로 이동되고 마커가 찍히게 된다!

근데 정확한 위치가 아니다.. 아무래도 IP주소에 해당하는 값으로 찍히는거 같은데 완전 동떨어진 곳이라 당황..

이것도 나중에 다시 정확한 방법을 찾아봐야겠다.

 

 


 

 

 

✅검색결과에 나온 충전소 위치들의 lat,lng(위도,경도) 이용해서 카카오지도에 전달 + 마커표시

 

사용하고 있는 API에서 lat,lng(위도,경도)도 같이 제공하고 있어서 이걸 받아서 사용했다.

일단 .info에 cursor: pointer;를 줘서 선택을 할 수 있다고 표시했다.

 

그리고 api 호출 시 lat,lng(위도,경도)를 가져오게 했다.

    for (let i = 0; i < items.length; i++) {
        const statNm = items[i].getElementsByTagName("statNm")[0].textContent;	
        const addr = items[i].getElementsByTagName("addr")[0].textContent;		
        const useTime = items[i].getElementsByTagName("useTime")[0].textContent;
        const output = items[i].getElementsByTagName("output")[0].textContent;	
        const type = items[i].getElementsByTagName("chgerType")[0].textContent;	
        const stat = items[i].getElementsByTagName("stat")[0].textContent;		
        const limitYn = items[i].getElementsByTagName("limitYn")[0]?.textContent || "N"; 
        const limitDetail = items[i].getElementsByTagName("limitDetail")[0]?.textContent || ""; 
        const lat = parseFloat(items[i].getElementsByTagName("lat")[0].textContent); 	// 위도
        const lng = parseFloat(items[i].getElementsByTagName("lng")[0].textContent);	//경도

 

 

그리고 리스트 결과값에 대한 항목을 클릭 시 카카오 지도에 해당 위도,경도를 전달해서 마커가 찍히게 했다.

// 리스트 클릭 시 해당 위치로 이동 및 마커 표시
listItem.addEventListener("click", function () {
    const selectedLat = parseFloat(this.dataset.lat);
    const selectedLng = parseFloat(this.dataset.lng);

    // 선택된 위치로 지도 이동 및 마커 표시
    const selectedLocation = new kakao.maps.LatLng(selectedLat, selectedLng);
    map.setCenter(selectedLocation);

    // 마커 생성, 지도 표시
    const marker = new kakao.maps.Marker({
        position: selectedLocation,
        map: map
    });
});

resultList.appendChild(listItem);
}

 

독립기념관을 클릭했는데 해당 위치로 잘 이동되면서 마커도 찍혔다!

독립기념관 클릭!

 

 

 


 

 

 

✅충전기 타입에 대한 필터링 부분 적용하기

 

 

충전기 타입에 대한 필터링도 적용했다.

5가지 충전타입을 두고 사용자가 체크해서 선택해서 필터링으로 보여지게 했다.

지금 사용하고 있는 API에서는 각 충전기 타입별로 결과를 나타내는 부분이 없어서

(제가 못찾는거일수도 있어요,,)

 

일단은 모든 API를 호출하고 해당 체크박스에 대한 필터링으로 제공했다.

 

<div class="chargetype">
    <label><input type="checkbox" value="chargetypeAllcheck" checked> 전체</label>
    <label><input type="checkbox" value="04" checked> DC콤보</label>
    <label><input type="checkbox" value="01" checked> DC차데모</label>
    <label><input type="checkbox" value="07" checked> AC3상</label>
    <label><input type="checkbox" value="02" checked> 완속</label>
</div>

 

지금 이렇게 체크박스 선택 옵션으로는 DC콤보, DC차데모, AC상, 완속 이렇게 제공하고 있다.

교차로 선택이 가능하며 하나만 선택해도 해당하는 값이 포함되어있으면 다 나오게 했다.

그러기 위해서 일단 선택한 타입에 대해 맵핑을 일단 다 해줬다.

01을 선택하면 01,03,05,06의 결과값이 필터링 되게

02가 선택되면 02,08 값이 선택이 되게

... 이런식으로 했다. 나중에 어떤 값을 쓰게될지 모르니 일단 다 추가!

// 필터링 후 결과 표시
function filterAndDisplayResults() {
    // 선택된 타입 체크박스 값 배열로 수집
    const selectedTypes = Array.from(document.querySelectorAll('.chargetype input:checked'))
        .map(checkbox => checkbox.value)
        .filter(value => value !== 'chargetypeAllcheck'); // 전체 체크박스 제외

    // 전체가 선택된 경우 모든 데이터를 필터링 없이 표시
    if (selectedTypes.length === 0 || selectedTypes.includes('chargetypeAllcheck')) {
        displayResults(fetchedItems.slice((currentPage - 1) * numOfRows, currentPage * numOfRows));
        totalPage = Math.ceil(fetchedItems.length / numOfRows);
        setupPagination();
        return;
    }

    // 타입 코드와 각 타입 조합 설정
    const chargerTypeMap = {
        "01": ["01", "03", "05", "06"], // DC차데모
        "02": ["02", "08"],             // AC완속
        "03": ["03", "06"],             // DC차데모+AC3상
        "04": ["04", "05", "06", "08"], // DC콤보
        "05": ["05", "06"],             // DC차데모+DC콤보
        "06": ["06"],                   // DC차데모+AC3상+DC콤보
        "07": ["07", "03", "06"],       // AC3상
        "08": ["02", "08"],             // DC콤보(완속)
        "89": ["89"]                    // H2
    };

    // 선택된 타입이 각 아이템의 타입에 포함되는지 확인하여 필터링
    const filteredItems = fetchedItems.filter(item => {
    const chgerType = item.getElementsByTagName("chgerType")[0].textContent;

    // 선택된 타입 중 하나라도 충전기 타입 코드에 포함되는지 확인
    return selectedTypes.some(selectedType => {
        // 선택된 타입이 포함된 조합 목록에 현재 충전기의 타입이 있는지 확인
        return chargerTypeMap[selectedType] && chargerTypeMap[selectedType].includes(chgerType);
        });
    });

    totalPage = Math.ceil(filteredItems.length / numOfRows); // 필터링된 데이터 기준으로 페이지 수 계산

    // 현재 페이지의 항목만 표시
    const currentPageItems = filteredItems.slice((currentPage - 1) * numOfRows, currentPage * numOfRows);
    displayResults(currentPageItems); // 필터링된 데이터 화면에 표시
    setupPagination(); // 페이지네이션 버튼 재설정
}

 

그렇게 하고 테스트를 해봤다.

 

DC차데모 선택시

DC차데모 선택시 DC차데모와 같이 중복된 충전기도 전부 표시되게 잘 나온다!

한번 더 AC3상 선택해서 테스트!

AC3상 테스트

 

DC차데모 + AC3상 + DC콤보

DC차데모 + AC3상

이렇게 AC3상이 들어가있는 모든 충전기의 값들이 잘 필터링되어져서 나오고 있다!

 

일단 오늘은 여기까지 작업했다!

 

이제 네이티브..해서 어플 화면으로도 구현해야한다!

 


 

 

 

*더 추가해야할 부분*

리스트 클릭 시 해당 지역 마커에 충전소 상세정보 팝업 제공

리스트 클릭 시 해당 지역을 길 찾기로 넘겨서 길 찾기 정보 제공

길 찾기 스타일링

 

이정도면 끝날듯 싶다!