Tech Story

[ Flutter / Python ] 좌표 (위도, 경도) 값으로 국내/해외 판단

쭝허니 2024. 1. 22. 13:29

요즘 위치정보 관련한 나만의 프로젝트를 진행하고 있는데 POI, Geocoding, Reverse Geo, 경로찾기 등 지도 위에서 할수 있는 많은 기능들이 필요한 상황이다. 그중 국내 POI정보는 우리나라 지도제공업체가 너무도 좋은데 결정적으로 무료 조회건수를 넘어가면 유료로 사용해야 하는 기능들이다. (현재 Naver, Kakao, SK, V-World 이렇게 사용해 봤는데 개인적으로 V-World API 가 가장 좋은 것 같다. 정부에서 제공하는 API 서비스이며 내게 필요한 서비스들은 거의 무료로 이용이 가능하다.)

다국어 버전을 지원하려고 하다보니, 국내 지도에서 필요한 POI검색, Geocoding, Reverse Geo 부분은 V-World API를 사용하고 우리나라를 제외한 모든 국가에서는 Python 의 geopy 를 사용한다. (이 또한 무료건수가 정해져 있기는 하나, 해외 버전은 추후 다시 고민하고자 한다.)
지도는 OSM(OpenStreetMap) 을 사용하고 있다.
이러한 상황이다 보니 해외 정보를 사용할때는 geopy 로 만든 API를 호출하고 국내 정보를 사용할때는 V-World API를 호출하려고 했는데, 딸랑 좌표( 위도/경도 )  정보만 가지고 국내외 구분을 하려니 도대체가 방법을 모르겠다는...... 한참 고민했는데 누군가의 블로그에서 그 해답을 찾았다. (근데 그 블로그를 못찾겠다. 지금 설명하려는 멋진 방법을 일러줬는데...... 추후 혹 다시 찾게 되면 출처를 분명히 하도록 하겠다.
여기에 잘 설명되어 있다 - https://bowbowbow.tistory.com/24)

결론부터 쉽게 설명하자면 주어진 위치정보의 좌표값이 해당 영역 Polygon 내에 존재하는지 판단하는 방법은 좌표값의 경도보다 크고, 좌표의 위도(latitude)값을 포함하는 Polygon 선분의 교차점 수가 짝수 인지 홀수 인지만 판단하면 된다.
홀수 이면 Polygon 영역 내부에 존재(국내)하며, 0 또는 짝수 이면 Polygon 영역 외부에 존재(해외) 하는것으로 정의할 수 있다.

좌측 경우 중심좌표의 경도보다 크고 Polygon 동일한 위도와의 교차점 갯수 5개 이므로 국내
우측의 경우 중심좌표의 경도보다 크고 Polygon 동일한 위도와의 교차점 갯수 4개 이므로 해외 라고 판단한다.

자 그럼 판단 방식은 알았으니 Flutter로 구현해 보자.
우선 우리나라를 구분하기 위한 Polygon 값을 통해서 지정해 주자. (필자는 GoogleMap 에서 좌표를 읽어오는 것이 가장 편했던 것 같다.)

좌측의 그림과 같이 임의로 우리나라를 경계지을 수 있는 Polygon 꼭지점들을 지정하고 그 좌표값(위도/경도)들을 Polygon List변수에 저장해 둔다.
그리고 Parameter로 전달받은 좌표가 국내인지 해외인지 판단 여부는, Loop를 돌면서 전달 좌표의 위도를 포함하는 Polygon 선분인지, 그리고 전달 좌표의 경도보다 큰 값을 포함하는 Polygon의 접점 갯수를 판단하면 된다. 좌측 그림의 경우 Polygon을 구성하는 각각의 선분 갯수는 10개 이므로 전달좌표의 경도값보다 크고 전달좌표의 위도값을 포함하는 각 선분의 교차점 수를 확인하면 된다.

하나 주의해야 할 점은 Polygon 영역을 설정할때 위도와 경도의 범위이다. 경도값의 범위는 영국 본초자오선을 기준으로 동경 (+)180˚ 서경 (-)180˚ 이므로 본초자오선(0˚) 이하 서경이 포함되는 국가의 Polygon을 설정할때는 경도 범위가 -180˚  ~  +180˚  임을 감안하여 수정해야 할 것이다. 또한 위도의 경우 북반구/남반구 로 구분되어지며 -90˚ ~ +90˚ 이므로 잘 구분해서 사용하면 될 것 같다.

필자의 경우 우리나라만 구분하면 되었기 때문에 서경 처리는 들어가 있지 않다.

 

// ==========================================================================================
// 해당 위치의 국내 여부를 Return (국내이면 V-World API Call, 아니면  GeoPY Call)
// ==========================================================================================
Future<bool> getKoreaInOut(LatLng param) async {
  List<LatLng> koreaPolygon = [
    LatLng(37.6831695, 125.6191259),
    LatLng(38.3667171, 127.1262549),
    LatLng(38.66153134451637, 128.11958997893478),
    LatLng(38.83072480089188, 128.33652521686935),
    LatLng(37.23575633088098, 132.1635851464025),
    LatLng(34.76303366422911, 129.3075404873899),
    LatLng(32.447247860726925, 126.6921063701961),
    LatLng(33.8033764, 124.7198601),
    LatLng(37.128143428704334, 124.27747776186246)
  ];

  int pLen = koreaPolygon .length;
  if (pLen < 3) {
    return false;
  }

  bool isIn = false;
  int prev = pLen - 1;

  for (int cur = 0; cur < pLen; cur++) {
    LatLng curPos = koreaPolygon [cur];
    LatLng prevPos = koreaPolygon [prev];
    if (max(curPos.latitude, prevPos.latitude) > param.latitude && min(curPos.latitude, prevPos.latitude) <= param.latitude) {
      if (curPos.longitude +
             ((param.latitude - curPos.latitude) / (prevPos.latitude - curPos.latitude)) *
                     (prevPos.longitude - curPos.longitude) > param.longitude) {
        isIn = !isIn;
      }
    }
    prev = cur;
  }
  isIn ? showToast('국내', 't') : showToast('해외', 't');
  return isIn;
}

위 코드와 같이 우리나라를 경계할 수 있는 Polygon영역을 좌표로 지정한 후, 직선 방정식 y2 - y1 = M * (x2 - x1) 의 x좌표를 경도로 y좌표를 위도로 치환시켜 작성한 코드이다. 이를 실행해 보면 국내외 판단결과를 정상적으로 리턴해 주는 것을 확인할 수 있을 것이다.
필자의 경우 해당 위치의 POI검색 또는 Geocode/RevGeocode API 호출 시 국내이면 V-World API를 호출하고, 해외의 경우 ORM의 API호출 하도록 작성했다.