저희 팀에서 제작한 서비스인 RooTrip은 기존의 SNS에서 지도를 추가한 여행용 SNS인데요, 실제 사용자들이 저희 플랫폼을 이용하면서 여행을 하는 느낌을 주자라는 취지에 제작이 되었기에 지도와의 상호작용 기능을 구현해야 했습니다.
그래서 오늘은 제가 인터랙션 기능을 어떻게 구현했는지에 대해 소개를 드리려고 합니다.
📕 목차
- 게시글 작성부터 시작해볼까?
- exifr 활용해서 메타데이터 추출하기
- 메타데이터 내부에 좌표가 없는 경우
- Popup 이용해서 위치 물어보기
- Heic 이미지 다루기
RooTrip SNS에서 게시글 작성은 아래와 같은 순서로 진행되고 있었습니다.
되게 단순하기에 쉽고 빠르게 게시글을 작성할 수 있다는 장점이 있었지만, 게시글 작성간에 지도와의 인터랙션이 전혀 되고 있지 않아서 지도의 필요성이 전혀 느껴지지 않았습니다.
그래서 팀원들과 함께 여행용 SNS라는 플랫폼에 걸맞게 지도를 조금 더 활용하는 방법에 대해 회의를 진행하였고, 이에 저희 팀은 사용자가 업로드한 이미지의 메타 데이터를 추출해 지도와 인터랙션 하는 기능을 추가하기로 결정하였습니다.
우선 사용자가 업로드한 이미지에 대한 정보를 가져오기 위해 exifr이라는 라이브러리를 활용하였습니다. exifr 문서에서는 실제 이미지에서 GPS 좌표뿐만 아니라 이미지의 데이터를 다루는 다양한 메서드를 제공해주는 것을 확인할 수 있었고, 이를 바로 프로젝트에서 적용해본 결과 아래와 같이 이미지의 정보가 출력되는 것을 확인할 수 있었습니다.
오우,, 이미지 내부에 이렇게나 많은 데이터가 존재한다니.. 신기하네요. 위 메타데이터에서 필요한 좌표만 가져와보겠습니다. latitude
, longitide
를 추출하면 이미지의 좌표를 얻을 수 있겠네요! 근데 추출하려고 보니 GPS관련 정보도 확인할 수 있엇습니다. GPSLatitude
랑 GPSLongitude
는 뭐지,,? 쟤들은 왜 배열이지...?
오,, GPT를 통해 GPSLatitude
, GPSLongitude
와 latitude
, longitude
의 차이를 이해할 수 있게 되었습니다!
요약하자면! GPSLatitude와 GPSLongitude는 GPS 데이터가 EXIF 포맷으로 저장될 때 사용되는 필드 이름으로 각각 도(degree), 분(minute), 초(seconds)를 나타내고, latitude와 longitude는 해당 좌표를 소수점 형태로 변환한 값으로 위도와 경도를 나타냄.
그렇다면 latitude
와 longitude
를 활용해서 이미지의 좌표 정보를 이용해서 지도에 이미지를 표시해보겠습니다!
드디어 이미지의 메타 데이터를 추출해서 지도에 해당 이미지를 나타낼 수 있게 되었습니다. 와 너무 좋아 너무 잘 만들었어.. 나 자신 칭찬해.
한번 테스트 해볼까?? 영차!
?????????????????????? 이건 왜 안돼지..? 모든 이미지에 메타 데이터가 들어가 있는거 아니였나? 메타 데이터에 대해 정확히 알고 있는지 확인하기 위해, 우선 메타 데이터에 대해 찾아보았습니다....
메타데이터
이미지의 메타데이터는 이미지 파일에 포함된 추가 정보를 말합니다. 이 정보는 이미지의 내용, 이미지가 생성 또는 수정된 날짜와 시간, 이미지를 촬영한 카메라의 모델, 위치 정보, 저작권 정보와 같은 이미지 자체의 데이터와는 별개로, 이미지와 관련된 상세한 컨텍스트를 제공합니다.
아,, 그렇구나 사람이 만든 이미지는 좌표를 직접 넣지 않는 이상 추출할 수 없구나.. 카메라를 이용해서 찍은 이미지에 대해서만 좌표를 추출할 수 있구나.
그럼 메타데이터 내부에 좌표가 없는 경우에는 어떡하지,,,????? 사용자가 일일이 지도 드래그하면서 위치 찾아서 넣어줘야하나? 너무 사용자에게 큰 부담을 주는 것 같은데..
우선,, 이미지의 좌표를 추출할 수 없는 경우라 할 지라도 사용자가 자신이 업로드한 이미지의 좌표를 직접 설정할 수 있어야 한다고 생각해서 react-map-gl 문서를 뒤져보았습니다.
다행히도, onClick으로 가져오는 MapLayerMouseEvent가 lngLat을 제공해주는 것을 알 수 있었고 지도상에서 클릭한 좌표를 쉽게 가져와서 이미지에 적용할 수 있었습니다.
import MapGL from 'react-map-gl';
const Map = () => {
const MapGLRef = useRef();
// ..
const setCoordinateOnMarker = (event) => {
// 클릭 이벤트로 받아오는 파라메터에서 실제로 좌표를 얻을 수 있음.
const { lat, lng } = e.lngLat;
// 좌표정보 저장
}
return (
<div className='map-container'>
<MapGL
// ...
ref={MapGLRef}
onClick={setCoordinateOnMarker}
/>
</div>
);
};
위와 같이 코드를 수정한 결과는 과연!!!
오우 제가 서울을 가고 싶어서 서울로 찍어봤는데, 아주 잘 찍히네요!
근데 바로 찍히는게 마음에 안드는군요. 자기가 표시한 위치가 맞는지 물어보는 기능을 추가해보겠습니다. (스스로를 성장시키는 방법...? 할일이 2배가 되고 3배가 되고..)
우선 아까전에 만들어뒀었던 setCoordinateOnMarker
메서드를 네이밍을 onShowPopup으로 변경해주고 Popup창과 현재 클릭한 좌표에 표시할건지를 확인하는 기능을 추가해보겠습니다.
아! 물론 좌표를 출력해주면 사용자는 모르겠죠? 그러니까 백엔드가 개발해놓은 역지오 API를 함께 활용하겠습니다.
const Map = () => {
const MapGLRef = useRef();
const [popupInfo, setPopupInfo] = useState({});
const [isShowPopup, setIsShowPopup] = useState(false);
// ..
// 현재 클릭한 좌표에 팝업을 표시하는 함수
const onShowPopup = async (e) => {
const { lat, lng } = e.lngLat;
const address = await getReverseAddress(lat, lng); // 현재 좌표의 주소를 받아오기
setIsShowPopup(true); // 팝업 표시하기
setPopupInfo({ lat, lng, address });
};
// 현재 클릭한 좌표가 맞다면, 이미지의 좌표를 설정하는 함수
const setLocationFile = () => {
const { lat, lng } = popupInfo;
dispatch(setCoordinateFile({ latitude: lat, longitude: lng }));
setIsShowPopup(false);
};
return (
<div className='map-container'>
<MapGL
// ...
ref={MapGLRef}
onClick={setCoordinateOnMarker}
/>
{isShowPopup && (
<CustomPopup
info={popupInfo}
onClose={() => setIsShowPopup(false)}
setLocation={setLocationFile}
/>
)}
</div>
);
};
한 이미지만으로 하면 재미없으니까 이번에는 다른 이미지로 테스트를 해보겠습니다.
짠! 선택한 장소가 맞는지 확인해보고, 맞다면 이미지의 좌표를 사용자가 직접 설정할 수 있게 만들었습니다. 근데 여기서 친구가 이상한 이미지 하나를 가져옵니다. 바로 heic 이미지... 말이죠.. 클라이언트 화면에서 이미지가 안나오네요..?
아니 이게 도대체 무슨일인가..... heic 이미지에 대해 찾아보니,
HEIC
HEIC (High Efficiency Image Format)는 이미지를 압축하여 저장하기 위한 파일 형식 중 하나입니다. HEIC는 주로 Apple의 iOS 기기에서 사진을 저장하는 데 사용됩니다. 이 형식은 JPEG보다 높은 압축 효율을 제공하며 이미지 품질을 유지하는 데 효과적입니다.
아, HEIC 이미지는 압축 파일이구나, 그럼 압축 풀어주고 이미지를 사용해야겠구나. 하 하 하 하 하
사용자한테 heic는 이미지는 올리지 마세요~ 라고 할 수는 없으니까 heic 이미지도 한번 다뤄봅시다.
저는 우선 heic 이미지의 압축을 풀어주고, 화면에 표시하기 위해서 jpg로 변환하는 과정이 필요하다고 생각했습니다. 검색을 통해 확인해보니 많은 사람들이 heic 이미지를 다루는 것을 확인할 수 있었고, heic2any 라이브러리를 활용해서 이미지 타입을 jpeg로 변환할 수 있었습니다.
const createHeicImageURL = async (fileInfo) => {
const convertedJPEG = await heic2any({
blob: fileInfo,
toType: 'image/jpeg',
}).then(
(resultBlob) =>
new File([resultBlob], `${fileInfo.name.split('.')[0]}.jpg`, {
type: 'image/jpeg',
lastModified: new Date().getTime(),
}),
);
return URL.createObjectURL(convertedJPEG);
};
우선 코드에 대해 설명드리겠습니다. 위 코드는 heic 이미지 포맷을 jpeg 포맷으로 변환하고, 변환된 이미지의 URL을 반환하는 함수입니다. 포맷 변환이 끝난 이후 사용자에게 이미지를 보여주어야 하기 때문에 await
를 활용해 비동기적으로 실행합니다.
우선 blob으로 만들어주고, blob을 풀어서 URL을 생성해줍니다.
🤔 blob으로 만들고 다시 풀어서 URL을 만들거면 왜 이 과정을 거칠까?
① blob으로 만들어주는 과정과 ② blob을 풀어주는 복잡한 과정을 거치는 이유는 기존의 heic 이미지 포맷에서는 createObjectURL을 활용하여 이미지를 나타낼 수 없기 때문입니다. 이러한 이유 때문에, ① 기존 heic 이미지를 image/jpeg 형태의 blob으로 만들어주고 ② image/jpeg 형태로 만들어진 blob을 풀어서 파일 형태로 만든 뒤 createObjectURL로 이미지를 나타냅니다.
휴! 드디어 heic 이미지도 처리할 수 있게 되었네요. 그렇다면 한 번 업로드해서 결과를 봅시다.
오~ heic 파일도 이미지가 잘 나오네요!! 여기까지 온 나 자신 진짜 칭찬해
오늘은 이미지의 메타 데이터를 추출해서, Mapbox에 이미지를 올리는 것을 포스팅해보았습니다. 이미지의 메타 데이터와 지도를 활용한 포스팅이 별로 없다보니 쪼끔 힘들긴 했지만, 생각보다 정말 재미있는 경험이였던 것 같습니다 ! ㅎㅎ
다음 게시물에서는 이미지의 메타 데이터를 추출해서, 지도를 확대하고 축소하는 인터랙션 기능을 어떻게 직접 구현했는지에 대해 다뤄보겠습니다.
긴 글 읽어주셔서 감사합니다!