본문으로 바로가기
728x90
반응형
SMALL

PolyCARP를 활용한 드론 Geofence/Area 공간 판단 구현기 - 좌표계, 경계선 그리고 수많은 삽질들

드론 안전 제어 시스템을 개발하면서 가장 먼저 구현해야 했던 기능 중 하나는 "공간 판단(Spatial Evaluation)" 이었다.

겉으로 보기에는 단순해 보인다.

드론이 비행 가능 구역 안에 있는가?
드론이 비행 금지 구역에 진입했는가?

하지만 실제 구현을 시작해보니 생각보다 훨씬 어려운 문제였다.

특히 실제 운영 환경에서는 단순히 내부/외부 여부만 알면 되는 것이 아니라

  • 현재 비행 가능 구역(Area) 안에 있는가
  • 비행 금지 구역(Geofence)에 진입했는가
  • 경계선까지 얼마나 남았는가
  • 위험 상태라면 어디로 복귀시켜야 하는가

까지 판단해야 했다.

이번 글에서는 NASA PolyCARP 기반의 공간 판단 기능을 구현하면서 겪었던 경험과 트러블슈팅 내용을 정리해보려고 한다.


왜 PolyCARP를 선택했는가

처음에는 단순히 위도/경도 범위를 비교하는 방법도 고려했다.

if(lat >= minLat &&
   lat <= maxLat &&
   lon >= minLon &&
   lon <= maxLon){
}

하지만 실제 비행 구역은 대부분 사각형이 아니다.

예를 들어 아래와 같은 다각형 형태일 수 있다.

          *
        *   *
      *       *
    *           *
      *       *
        *   *
          *

이 경우 min/max 방식으로는 정확한 판단이 불가능하다.

결국 다각형 기반 공간 판단이 필요했고 PolyCARP의 Polygon Geometry 개념을 활용하게 되었다.


첫 번째 문제 - 위경도는 생각보다 계산하기 어렵다

처음에는 위도/경도 좌표만으로 모든 계산을 시도했다.

하지만 곧 문제가 발생했다.

위도 0.0001 차이
경도 0.0001 차이

가 항상 같은 거리가 아니다.

특히 경도는 위도에 따라 실제 길이가 달라진다.

결국 모든 계산을 Local 좌표계로 변환하기로 했다.

x = (lon - refLon)
      * 111320
      * cos(refLat)

y = (lat - refLat)
      * 111320

변환 후에는

x = 동서 방향 거리(m)
y = 남북 방향 거리(m)

로 표현할 수 있게 되었다.

이후 Polygon 포함 여부, 경계 거리 계산, 복귀 지점 계산이 훨씬 단순해졌다.


두 번째 문제 - GeoJSON 좌표 순서 함정

구현 후 이상한 현상이 발생했다.

분명 Geofence를 적재했는데 조회 결과가 항상 0건이었다.

디버깅해보니 Spatial Index가 비어 있었다.

원인을 찾아보니 GeoJSON 좌표 순서를 잘못 해석하고 있었다.

GeoJSON은

[
  127.123,
  37.123
]

형태로 저장된다.

[lon, lat]

순서이다.

하지만 나는 무의식적으로

double refLat = coordinate[0];
double refLon = coordinate[1];

로 작성해버렸다.

결과적으로

위도 = 127
경도 = 37

이라는 말도 안 되는 좌표가 생성되었다.

Spatial Index의 Bounding Box도 전부 깨졌고 Geofence 검색 결과는 항상 0건이 되었다.

수정 후 정상적으로 동작했다.

double refLon = coordinate[0];
double refLat = coordinate[1];

단순한 실수였지만 실제로 하루 가까이 원인을 찾지 못했던 버그였다.


세 번째 문제 - Polygon 내부 판정 버그

어느 날 테스트 중 이상한 결과가 나왔다.

Geofence 밖에 있는 드론이 내부로 판정되고 있었다.

원인을 추적해보니 Polygon 마지막 점을 시작점과 동일하게 넣고 있었다.

A
B
C
D
A

Polygon을 닫기 위해 마지막 점을 중복 추가했던 것이다.

문제는 마지막 선분이

A -> A

가 되면서 길이가 0인 선분이 생성된다는 점이었다.

이 선분 때문에 Boundary Projection 계산이 꼬였고 contains() 결과까지 잘못 나오고 있었다.

이후에는

Polygon은 중복 없이 저장

하거나

if(segmentLength == 0){
    continue;
}

방어 코드를 추가해서 해결했다.


네 번째 문제 - 고도 단위(ft / m)

이 버그는 찾는 데 정말 오래 걸렸다.

수평 위치는 분명 Area 내부였다.

그런데 수직 판정이 계속 실패했다.

Position.makeLatLonAlt(
    lat,
    lon,
    100
);

고도를 100m로 넣었다고 생각했는데 실제로는 아니었다.

NASA 라이브러리의 기본 단위는 feet였다.

100ft
≈ 30m

로 해석되고 있었던 것이다.

이 때문에

현재 고도 = 30m
Area 최소고도 = 100m

로 판단되어 계속 위험 상태가 발생했다.

결국 모든 Position 생성부를 아래처럼 수정했다.

Position.makeLatLonAlt(
    lat,
    "deg",
    lon,
    "deg",
    altitude,
    "m"
);

이후 수직 판단이 정상적으로 동작했다.


다섯 번째 문제 - Geofence와 Area는 복귀 방향이 반대다

초기 구현에서는 Geofence와 Area 모두 같은 복귀 알고리즘을 사용했다.

그런데 테스트를 해보니 이상했다.

Area 밖에 있는 드론이 더 멀리 도망가고 있었다.

곰곰이 생각해보니 두 개념은 완전히 달랐다.

Geofence는

위험구역

이다.

따라서 내부에 있으면 밖으로 나가야 한다.

반면 Area는

허용구역

이다.

따라서 외부에 있으면 안으로 들어와야 한다.

결국 복귀 알고리즘을 분리했다.

Geofence

현재위치
→ 경계점
→ 경계 밖

Area

현재위치
→ 경계점
→ 중심 방향 내부

이렇게 방향 자체가 반대였다.


최종 구조

현재 공간 판단 구조는 다음과 같다.

TrackPosition
      ↓
Local Projection
      ↓
Polygon Contains
      ↓
Boundary Distance
      ↓
Area / Geofence Evaluation
      ↓
Recovery Point Calculation
      ↓
Alert & Avoidance Command

평가 로직과 회피 로직을 분리하면서 테스트도 훨씬 쉬워졌다.


마무리

처음에는 "다각형 안에 있는지만 확인하면 되겠지"라고 생각했다.

하지만 실제 구현 과정에서는

  • 좌표계 변환
  • GeoJSON 표준
  • 단위(ft/m)
  • Polygon Geometry
  • 경계 거리 계산
  • 복귀 지점 계산

등 예상보다 훨씬 많은 요소를 고려해야 했다.

특히 이번 프로젝트를 통해 배운 점은 공간 판단은 단순한 좌표 계산이 아니라는 것이다.

결국 중요한 것은

"현재 어디에 있는가?"

보다

"얼마나 위험한가?"

그리고

"어디로 이동해야 안전한가?"

를 판단하는 것이라는 사실을 다시 한번 느낄 수 있었다.

728x90
반응형
LIST