
요즘 다양한 feature에서 유저의 position 정보가 필요한 경우가 많은데요.
특히 코로나 이후에 야외 활동이 활성화되면서,
위치 정보를 기반으로한 서비스에 대한 수요가 증가하고 있습니다.
지도에 사용자 위치를 표시해주기, 위치 기반 실시간 서비스 개발을 위해서는
HTML5부터 제공되는 Geolocation API를 사용하시면 됩니다!
MDN 공식 문서에 따르면,
Geolocation API는 유저 동의 하에
웹 어플리케이션에서 위치 정보에 접근할 수 있는 API입니다.
navigator.geolocation
을 통해 Geolocation API에 접근할 수 있어요.
해당 API의 진입점이고, Geolocation 객체 인스턴스를 리턴해요.
위치 정보를 가져오는 방식은 2가지 다른 메서드로 제공돼요.
상황에 맞춰서 이용하시면 됩니다!
(1) 장치의 현재 위치 가져오기 (일시적) :
Geolocation.getCurrentPosition(param1, optionalParam2, optionalParam3)
(2) 기기의 위치가 바뀔때마다 새로운 위치 사용 (위치 바뀔때마다 등록된 함수 호출) :
Geolocation.watchPosition(param1, optionalParam2, optionalParam3)
두개 메서드의 매개변수는 3개로 동일해요.
(콜백 이름은 이해를 돕기 위해 임의로 작성했습니다 🤩)
매개변수1 onSuccess 콜백 (필수) - 위치 정보를 성공적으로 가져왔을 때, 위치 데이터를 담은 GeolocationPosition 객체만을 매개변수로 해당 콜백을 호출해줘요!
매개변수2 onError 콜백 (선택) - 위치 정보 가져오기에 실패했을 때, 실패 원인을 담은 GeolocationPositionError 객체를 매개변수로 해당 콜백을 호출합니다.
매개변수3 객체 (선택) - 위치 정보 회수를 위해 사용할 옵션을 지정할 수 있어요.
옵션은 enableHighAccuracy(브라우저에 정확한 위치 정보 요청), timeout(위치 정보 얻을 때까지 기다릴 시간을 밀리초 단위로 지정), maximumAge(캐시된 위치 정보의 유효 시간을 밀리초로 지정하고, default 값은 0입니다.)가 있어요.
먼저 예시를 살펴보고 2편에서 더 상세히 뜯어보기로 할까요 😁
reverse geocoding을 위해서는 Big Data Cloud API를 활용했습니다.
// reverseGeolocation.ts
import axios from 'axios';
type ReverseGeocodedData = { // city name외에도 다양한 data를 제공합니다.
latitude: number;
longitude: number;
continent: string;
lookupSource: string;
continentCode: string;
localityLanguageRequested: string;
city: string;
countryName: string;
countryCode: string;
postcode: string;
principalSubdivision: string;
principalSubdivisionCode: string;
plusCode: string;
locality: string;
localityInfo: object;
};
export const fetchReverseGeocoded = async (
latitude: string,
longtitude: string
): Promise<ReverseGeocodedData> => {
const reverseGeocoded = await axios.get(
`https://api.bigdatacloud.net/data/reverse-geocode-client?latitude=${latitude}&longitude=${longitude}&localityLanguage=en`
);
return reverseGeocoded.data;
}
// location.ts
const getPostion = ():Promise<GeolocationPostion> => {
return new Promise((resolve, reject) =>
navigator.geolocation.getCurrentPosition(resolve, reject)
);
};
// LocationButton.js
import { getPosition } from './location.ts'
import { fetchReverseGeocoded } from './reverseGeolocation.ts'
export const LocationButton = ({ setCityName , setLoading } : {(cityName:string) => void, (loaded:boolean) => void }) => {
const [errorMsg, setErrorMsg] = useState<string>('');
const onLocationDataClick = useCallback(() => {
setLoading(true);
getPostion()
.then((positionData) => {
fetchReverseGeocoded(
positionData.coords.longtitude.toString(),
positionData.coords.latitude.toString(),
)
.then((locationData) => {
setCityName(locationData.city)
setLoading(false);
if(errorMsg){ setErrorMsg(''); }
})
.catch((e) => { // BigDataCLoud API Error
setLoading(false);
setErrorMsg('도시 이름을 가져오지 못했습니다. '));
})
.catch((e) => {
setLoading(false);
if(e?.code === 1){ // error code 1인 경우는 유저가 위치 정보 접근 권한을 거절한 경우를 의미합니다.
setErrorMsg('위치 정보 제공에 동의해주세요.');
return;
}
console.error(e);
})
}, [errorMsg])
return (
<button onClick={()=>onLocationDataClick()}>위치정보 활용🧭</button>
<div>{errorMsg}</div>
);
}
function onSuccess(position:GeolocationPostion){
doSomething(position.coords.latitude, position.coords.longtitude);
}
function onError(e){
if(e.code === 1){
alert('서비스 이용을 위해 위치 정보 제공에 동의해주세요!');
return;
}
if(e.code === 2){ // 타임아웃 발생
return;
}
console.error(e);
}
const options = {
enableHighAccuracy: true, // 정확한 위치정보 제공 - default 값은 false
maximumAge: 30000, // 캐시된 위치 정보의 유효 시간 - 밀리초
timeout: 27000 // 위치 정보 얻을 때까지 기다릴 시간 - 밀리초
}
const watchId = navigator.geolocation.watchPosition(onSuccess, onError, options)
const getPostionData(onSuccess, onError, options){
if(!navigator.geolocation){
alert('지원하지 않는 브라우저 입니다!');
return null;
}
return watchId = navigator.geolocation.watchPosition(onSuccess, onError, options);
}
const watchId = getPostionData(onSuccess, onError, options); // 유저의 위치 추적 시작
navigator.geolocation.clearWatch(watchId); // 유저의 위치 추적 종료