GPS

DU벅이는 API를 사용하지 않기 때문에 개발 초기에 GPS는 당연히 사용하지 못할 것이라고 생각했었다.
하지만 어느 날 저녁 문득 생겨난 아이디어로 간단하게나마 제작을 하게 되었다.

우선 휴대폰에서 GPS 요청을 보내면 어떤 정보를 반환하는지를 찾아보았다.

GPS 요청 결과 위도, 경도를 소수점 아래 6번째 자리까지 표시된 숫자로 반환해 준다.
이 때 소수점 아래 6번째 자리, 즉 0.000001 이 실제 1m를 의미한다.

DU벅이는 고정된 지도 이미지를 사용하므로 이미지 좌측 상단의 경도/위도, 우측 하단의 경도/위도를 알면
비례식을 통해 지도 이미지 위에 현재 위치를 나타낼 수 있을 것 같았다.

explanation1

위 사진과 같이 좌측 상단의 경도/위도가 ( $A_x$, $A_y$ ) = ( 126.996208, 37.564249 ) 이고
우측 하단의 경도/위도가 ( $B_x$, $B_y$ ) = ( 127.004660, 37.552279 ) 일 때

현재 위치(C)에서 GPS 요청을 보내서 받은 경도/위도가 ( $C_x$, $C_y$ ) = ( 127.002549, 37.560259 ) 라면
아래와 같은 비례식 계산을 통해 C의 위치 ( $dx$, $dy$ )를 구할 수 있다.

$dx = 3000 \times \frac{(C_x - A_x)}{(B_x - A_x)}$

$dx = 3000 \times \frac{0.006341}{0.008452} = 2250.709891$

$dy = 5333 \times \frac{(A_y - C_y)}{(A_y - B_y)}$

$dy = 5333 \times \frac{0.00399}{0.01197} = 1777.666667$

즉 점 C의 위치는 이미지 픽셀 ( 2250, 1777 ) 이 된다.

Geolocator를 통한 구현

위의 아이디어를 실제로 구현하기 위해서 Geolocator 플러그인을 사용했다. [Geolocator 링크]
Geolocator는 현재 위치 정보 반환, 위치 변경 감지, 위치 권한 관리, 위치 정보 저장 등과 같은
여러 기능을 제공해주는 Flutter 플러그인이다.

버전은 개발 당시 최신 버전이었던 7.6.2 버전을 적용했다.

GPS 기능은 아래 4가지 함수와 isTrackingLocation 변수를 통해 동작된다.

  • isTrackingLocation 변수(bool)
    현재 GPS 기능이 활성화 상태인지, 비활성화 상태인지 true/false로 나타내는 변수
    이 변수가 가지는 값에 따라 GPS 기능 버튼의 로고를 다르게 표시한다.
  1. requestLocationPermission 함수: 위치 권한을 요청하는 함수

    Permission.location.request() 메서드를 이용해서 위치 권한을 요청하며
    만약 권한 자체가 부여되지 않은 경우에는 사용자에게 권한 요청을 보낸다.

  2. toggleLocationTracking 함수: GPS 기능을 활성화/비활성화 하는 함수

    isTrackingLocation이 true라면, 즉 활성화 상태라면 positionStream?.cancel()을 통해
    비활성화 시키고 isTrackingLocation를 false로 만든다.
    isTrackingLocation이 false라면, 즉 비활성화 상태라면 startLocationStream 함수를 호출하여
    활성화 시키고 isTrackingLocation를 true로 만든다.

  3. startLocationStream 함수: 위치 스트림을 받아오는 함수

    우선 requestLocationPermission 함수를 호출하여 위치 권한을 요청한다.
    그 후 Geolocator.getPositionStream 메서드를 사용하여 위치 스트림을 받아온다.
    (위치 스트림이란 사용자의 현재 위치를 지속적으로 업데이트하는 데이터 스트림을 말한다.)

    정확도는 LocationAccuracy.high로 높은 정확도를 가지도록 설정했으며
    사용자가 일정 거리를 이동할 때마다 또는 일정 시간마다 위치를 업데이트할 수 있는데
    전자의 경우 적용해 본 결과 반응이 느리고 부정확한 면이 있어
    1초마다 위치 정보를 업데이트하도록 설정했다.

    받아온 경도/위도 정보를 gpsToPixel 함수를 통해 Offset으로 변환 후 nowLocationPixel 변수에 저장한다.

  4. gpsToPixel 함수: 비례식을 통해 경도/위도를 이미지 내 위치(Offset)로 변환하는 함수

    위 내용에 설명했던 비례식을 그대로 코드 구현해놓은 함수이다.

지도 이미지 위에 표시

기능적 구현을 완료했으므로 이제 반환한 이미지 픽셀 좌표에 나의 위치임을 나타내는 도형을 띄우기만 하면 된다.

나의 위치는 GPS 기능이 활성화된 상태에서만 보여야 하므로 isTrackingLocation = true 일 때만 보이도록 설정했다.
도형은 DU벅이의 테마색인 주황색을 활용하여 아래와 같이 만들었다.

figure

두 개의 원으로 이루어져 있으며 외부의 원은 투명도를 높였고 내부의 원은 흰색 테두리를 가지도록 만들었다.

해당 도형은 이미지를 띄운 것이 아니고 레이아웃 클래스 Positioned를 통해 만들었기 때문에
두 개의 원을 원하는 곳에 위치시키려면 약간의 수학 계산이 필요하다.

Positioned는 top, bottom, left, right과 같은 속성을 지정해서 부모 위젯 내에서 어디에 위치할지를 결정한다.
이때 지정해 준 위치에 Positioned의 중앙이 위치하는 것이 아니라 좌측 상단이 위치하게 된다.

GPS 기능을 통해 반환한 이미지 픽셀 위치에 정확히 원이 위치해야 하므로
그 도형의 크기에 맞게 좌표를 수정해야 한다.

explanation2

위 그림과 같이 큰 원은 ( -9, -9 ) 만큼, 작은 원은 ( -5, -5 ) 만큼 각각 빼주어야
내 위치가 각각의 원의 중앙에 오도록 이동시킬 수 있다.

더 나아가 가독성을 위해 줌 레벨에 따라 크기가 일정하기 하기 위해서 빼주는 만큼에서 줌 레벨 정도를 나눠줘야 한다.

즉 큰 원은 ( - 9 * 1.3 / mapvalue.scale, - 9 * 1.3 / mapvalue.scale ) 만큼,
작은 원은 ( - 5 * 1.3 / mapvalue.scale, - 5 * 1.3 / mapvalue.scale ) 만큼 빼주어야 한다.

결과는 아래와 같다.

explanation3

댓글남기기