[React] 접속한 사용자 위치의 날씨를 아이콘으로 나타내기 (Geolocation API & OpenWeatherMap API)

·2024년 10월 26일
1
post-thumbnail

Intro

작년에 진행한 프로젝트에서 사용자 위치를 기반으로 날씨 아이콘을 노출해야 하는 요구사항이 있었습니다. 이를 처리하기 위해 아래와 같은 과정을 진행했습니다.

  1. 사용자의 현재 위치 파악 (Geolocation API 사용)
  2. 사용자 현재 위치의 날씨를 파악하여 아이콘 출력 (OpenWeatherMap API 사용)

1. 사용자의 현재 위치 파악

브라우저에 내장된 Geolocation API를 사용하여 사용자의 현재 위치를 파악할 수 있습니다.
저는 사용자의 위치 정보를 받아오는 로직을 커스텀 훅으로 만들어 state에 위치 정보와 에러 정보를 넣어 반환했습니다.
Geolocation API의 특징과 현재 위치를 요청하는 방법, 최종 코드를 순서대로 소개하겠습니다.

Geolocation API 특징

  • 사용자의 실제 위치를 브라우저에게 요청
  • 개인 정보와 관련되어 있으므로 사용자에게 접근 허용을 받음
  • 브라우저가 지원하는 Geolocation API는 navigator.gelocation으로 정의
  • 세 가지 메서드가 존재 (getCurrentPosition, watchPosition, clearWatch)
  • 브라우저 호환성 전체 지원

현재 위치 요청

navigator.geolocation.getCurrentPosition(success, error, options)

getCurrentPosition 메서드를 통해 현재 위치를 요청할 수 있으며 아래와 같이 세가지 매개변수를 받습니다.

  • success : 요청 성공 시 호출되어 위치 정보를 받는 콜백함수
  • error(선택) : 요청 실패 시 호출되어 에러 정보를 받는 콜백함수
  • options(선택) : 옵션 값

success(성공 콜백함수)

const handleSuccess = (location) => {
  const { latitude, longitude } = location.coords
  setLocation({ latitude, longitude })
}

location 값을 콘솔에서 확인해보면 이미지와 같은 값들이 출력됩니다.
위의 코드는 위치 정보를 성공적으로 가져왔을 때 위도와 경도를 state에 저장합니다.
추후 날씨 정보를 받아오기 위해 OpenWeatherMap API를 사용할 때 위도, 경도 값만 필요하므로 해당 값만 구조분해할당으로 받아왔습니다.

error(에러 콜백함수)

const handleError = (error) => {
  setError(error.message)
}

브라우저에서 위치 정보 접근을 거부한 후 error 값을 콘솔에서 확인해보면 이미지와 같은 값들이 출력됩니다.
에러 메세지를 따로 커스텀할 필요는 없어서 message 값을 그대로 출력하였는데 만약 error.code 값에 따라 메세지를 커스텀하여 작성해야 된다면 아래와 같이 작성할 수 있을 것 같습니다.

const handleError = (error) => {
  switch (error.code) {
    case error.PERMISSION_DENIED:
      setError('사용자가 Geolocation API 사용 요청 거부')
      break

    case error.POSITION_UNAVAILABLE:
      setError('가져온 위치 정보 사용 불가')
      break

    case error.TIMEOUT:
      setError('위치 정보를 가져오기 위한 요청이 허용 시간 초과')
      break

    default:
      setError('확인되지 않은 오류 발생')
      break
  }
}

options(옵션값)

const options = {
  maximumAge: 0,
  timeout: Infinity,
  enableHighAccuracy: false
}
  • maximumAge(기본값: 0) : 위치 정보의 캐시 기한(밀리초)을 나타내는 양수 값. 0일 시 캐싱 되지않아 항상 최신 정보를 얻음
  • timeout(기본값: Infinity) : 위치 정보를 반환하는 데 허용되는 최대 시간(밀리초)을 나타내는 양수 값
  • enableHighAccuracy(기본값: false) : 위치 정보의 정확도 설정. true일 시 정확한 위치를 제공할 수 있다면 제공하지만 응답 시간이 느려지거나 전력 소모가 증가할 수 있음(e.g. 모바일 환경에서 GPS 위치 정보 사용)

프로젝트 환경에서 별도의 옵션 값은 필요하지 않았기 때문에 따로 작성하지 않았습니다.
최종적으로 완성된 커스텀 훅의 코드는 아래와 같습니다.

전체 코드

import { useEffect, useState } from 'react'

export default function useCurrentPosition() {
  const [location, setLocation] = useState()
  const [error, setError] = useState()

  const handleSuccess = (location) => {
    const { latitude, longitude } = location.coords
    setLocation({ latitude, longitude })
  }

  const handleError = (error) => {
    setError(error.message)
  }

  useEffect(() => {
    navigator.geolocation.getCurrentPosition(handleSuccess, handleError)
  }, [])

  return [location, error]
}

2. 사용자 현재 위치의 날씨를 파악하여 아이콘 출력

날씨 정보를 제공하는 서비스는 다양하지만, 국내 및 해외 날씨 데이터가 필요하여 OpenWeatherMap API를 채택했습니다. 개발 과정에서 무료 버전을 사용했습니다.
OpenWeatherMap : https://openweathermap.org
현재 날씨 API 문서 : https://openweathermap.org/current

2-1. API 응답 데이터 확인

위치 정보 커스텀 훅과, 날씨 API를 통해 아이콘을 노출하는 작업을 하기 전에 먼저 API가 반환하는 JSON 데이터를 확인하고 아이콘 파일을 public 폴더에 세팅했습니다.
아이콘 목록은 링크 에서 확인할 수 있습니다.

아이콘 이름의 d와 n은 낮과 밤의 차이로, 별도의 차이 없이 나타내기로 하여 숫자만 활용하여 아래와 같이 아이콘을 세팅했습니다.
이제 이 아이콘을 노출하는 컴포넌트를 만들기 위해 API key를 발급받고 사용해야 합니다.

2-2. API Key 발급 및 설정

REACT_APP_WEATHER_KEY=your_api_key_here

OpenWeatherMap에서 API Key를 발급받아 환경변수 파일(.env)에 추가합니다.

2-3. API 문서를 참조하여 날씨 정보 요청

https://api.openweathermap.org/data/2.5/weather?lat={lat}&lon={lon}&appid={API key}

위 URI를 통해 날씨 데이터를 JSON 형식으로 응답받을 수 있습니다.
실제 프로젝트에서는 svg를 svgr을 통해 컴포넌트로 만들어 사용하는 등의 환경 세팅으로 인해 코드가 복잡하여 글 작성을 위해 img 태그를 사용했을 때의 경우로 코드를 변형하고 주석을 통해 설명을 작성했습니다.
쉽게 풀어내기 위해 임의로 작성한 코드이기 때문에 에러 처리는 따로 하지 않았습니다.

import { useEffect, useState } from 'react'
import useCurrentPosition from '~/modules/useCurrentPosition'

export default function WeatherIcon() {
  // 커스텀 훅 데이터 사용
  const [userLocation, userLocationError] = useCurrentPosition()
  // 초기값을 설정하여 에러 상태, 데이터를 응답 받기 전에는 spinner 아이콘 노출
  const [weather, setWeather] = useState('spinner')

  useEffect(() => {
    // 위치 정보를 받아오지 못했을 시 에러 처리 및 return
    if (userLocationError) {
      console.error('weatherError', userLocationError)
      return
    }
    // 위치 정보 응답 시 아이콘 노출 설정
    if (userLocation) {
      fetch(`https://api.openweathermap.org/data/2.5/weather?lat=${userLocation.latitude}&lon=${userLocation.longitude}&appid=${process.env.REACT_APP_WEATHER_KEY}`)
        .then((response) => response.json())
        .then((data) => {
          // d, n 값을 제거하기 위해 slice 메서드 사용
          setWeather(data.weather[0].icon.slice(0, -1))
        })
    }
  }, [userLocation, userLocationError])

  return <img src={`/icons/ico_weather-${weather}.svg`} alt="현재 날씨" />
}

이렇게 사용하면 에러 또는 날씨 API 응답 전에는 spinner 아이콘이 노출되고, 응답 후에는 날씨에 맞는 아이콘이 노출됩니다.
응답 값을 확인해 보면 여러가지 데이터들이 있어 상황에 맞게 사용할 수 있을 것 같습니다.


마무리

사실 프로젝트 중에는 사용자가 위치 정보 접근을 승인하지 않으면 어떻게 처리할지, 어떤 날씨 서비스를 이용해야될지 등... 여러가지 고민할 거리가 많았는데 부가적인 내용은 제외하고 구현 방법에 초점을 맞춰 작성해봤습니다.
해당 프로젝트는 홀딩되어(...) 실제로 운영하지는 못해 운영 상의 정보가 빈약하고, 구글링을 해보면 다른 글들이 많아 작성할지 말지 고민이 많았지만 정리 겸 작성해봅니다. 다른 분들께도 도움이 되면 좋겠습니다!

profile
FE ✨

2개의 댓글

comment-user-thumbnail
2025년 3월 12일

이것이 바로 전설의 그 플젝인가요

1개의 답글