왜 필요한가요?
지도 관련 프로젝트를 진행하다가 사용자가 GIS 서비스를 통해서 맵을 돌아다닐 때, 특정 지역에 들어갔을 때, GIS 서비스의 전환이 필요한 상황이였습니다. 예로 들어서 국내 부동산 상품을 살펴보다가 (Naver Map 사용) 해외 부동산 상품을 살펴보러 갈 때 (Google Map 사용), 그리고 그 역도 마찬가지로 특정 좌표를 판단해야 서로 다른 지도 서비스의 전환이 가능합니다.
네이버든 구글이든 각 서비스는 장단점을 가지고 있었는데 위와 같이 판단한 이유는 다음과 같습니다.
네이버 지도
구글 지도
위 이유에 따라서 기능적 요구사항은 사용자가 현재 보고싶어하는 지도의 위치에 따라서 GIS 변환 알림창이 떠야하는 것입니다. 이를 위해서 Langtitude
와 Longitude
값으로 현재 사용자가 국내 지도를 보고있는지, 국외 지도를 보고있는지 판단해야한다. 이에 대해서 간단하게 자료 조사를 진행해보았습니다.
먼저 국내의 정의를 하기 위해서 대한민국의 지리정보에 대하여 조사를 진행해봤습니다.
대한민국 전체
극동: 경상북도 울릉군의 독도 동단 동경 131° 52′20" → 131.87222222
극서: 평안북도 용천군 신도면 마안도 서단 동경 124° 11′45" → 124.19583333
극남: 제주도 남제주군 대정읍 마라도 남단 북위 33° 06′40" → 33.11111111
극북: 함경북도 온성군 남양면 북단 북위 43° 00′35" → 43.00972222
북한 제외
극동: 경상북도 울릉군의 독도(獨島)로 동경 131° 52′20“, → 131.87222222
극서: 전라남도 신안군의 소흑산도(小黑山島)로 동경 125° 04′, → 125.06666667
극북: 강원도 고성군 현내면 송현진으로 북위 38° 27′00, → 38.45000000
극남: 제주도 남제주군 마라도(馬羅島)로 북위 33° 06′00" → 33.10000000
대한민국의 일반정보의 대략적인 경도범위는 125 - 132
, 위도범위는 33 - 39
입니다. 그러나, 이번 기능에서는 위 데이터가 쓰이지않을 것입니다.
사용자가 화면을 움직일 때, center 좌표를 기준으로 국내외를 판단해야 합니다. 그런데, 위 과정
을 진행 도중에 한 가지 이슈사항이 생겼습니다. 바로 역지오코딩
은 바다를 클릭할 시, 해당 좌표가 국내인지 국외인지 판단하기가 모호하다는 점입니다. 명백하게 국토를 Input으로 넣게되면 국내 주소가 반환되지만, 바다일 경우에는 그렇지 않았습니다.
Reverse Geocoding (역지오코딩)
좌표(위도, 경도)를 통해 주소 정보(법정동, 행정동, 지번주소, 도로명주소 등)를 반환합니다.
그렇기에 다른 해결 방안을 찾아야했고, 다음과 같이 진행하였습니다.
먼저 국내와 국외를 구분할 다각형을 그리고 해당 다각형의 각 꼭지점의 좌표를 구해줍니다.
(좌표는 위도와 경도입니다.)
다각형이 정의되었으면, 다각형 내부/외부 판별 알고리즘
을 사용하여, 현재 유저의 center 좌표가 국내인지 국외인지 판단해줍니다. 다각형 내부인지 외부인지 판별하는 기준은 다음과 같습니다.
먼저, 특정한 좌표(여기서는 center 좌표)에서 다각형의 X좌표 Max값까지 선(P)을 긋습니다. 그리고, 다각형의 선분과 선분 P의 교점의 개수를 구해줍니다.
- 교점의 개수가 0개
: 다각형과 교점이 없으므로 국외입니다.
- 교점의 개수가 홀수
: 국내입니다.
- 교점의 개수가 짝수
: 국외입니다.
이에 대한 조건은 다음과 같습니다.
점A의 y값과 점B의 y값 사이에 y값이 존재할 때
(cur.y < y && prev.y >= y)
// 또는
(prev.y < y && cur.y >= y)
y를 대입한 직선의 방정식의 값보다 x의 값이 클 때
y - y1 = M * (x - x1)
M = (y2 - y1) / (x2 - x1)
cur.x + ((y - cur.y) * ((prev.x - cur.x) / (prev.y - cur.y))) < x
위 조건들을 만족할 때, isInner = !isInner;
플래그가 변경됩니다.
위 방식을 모든 다각형의 점들에 대해서 반복문을 돌렸을 때, 해당 좌표가 다각형 내부에 있는지 외부에 있는지 확인할 수 있습니다. 실제 위 방식에 대해서 경도/위도 좌표에 대입했을 때, 코드는 다음과 같습니다.
/**
* 국내외 판별
* @param props
* @param props.lat Latitude - 위도
* @param props.lng Longitude - 경도
*/
const checkInsideKorea = ({ lat, lng }): boolean => {
const coordinateList = KoreaCoordinate;
const size = coordinateList.length;
if (size < 3) {
return false;
}
let isInner = false;
let followIndex = size - 1;
for (let cur = 0; cur < size; cur++) {
const curPos = coordinateList[cur];
const prevPos = coordinateList[followIndex];
if (
(curPos.lng < lng && prevPos.lng >= lng) ||
(prevPos.lng < lng && curPos.lng >= lng)
) {
/**
* 직선의 방정식: y - y1 = M * (x - x1)
* 기울기: M = (y2 - y1) / (x2 - x1)
*/
if (curPos.lat + ((lng - curPos.lng) / (prevPos.lng - curPos.lng)) * (prevPos.lat - curPos.lat) < lat) {
isInner = !isInner;
}
}
followIndex = cur;
}
return isInner;
};
다각형에 지구에서 먼 거리를 가로지르는 단일 면이 있는 경우 위 알고리즘은 3차원의 시각에서 본다면, 정확한 결과가 아닐 수도 있습니다. 더 정확하게 하려면, 다각형을 이루는 점의 개수가 많아져야 하지만 이 것은 또한 성능이 하락되게 됩니다.
다각형이 국제 날짜선을 넘으면 경도가 갑자기 재설정되어 알고리즘이 오류가 발생할 수 있습니다. 이러한 일이 발생하지 않도록 하려면 일부 모서리의 경도에 360°를 더하거나 빼야 할 수도 있습니다. (이마저도 다각형이 북극이나 남극을 완전히 돌면 그 솔루션은 작동하지 않습니다.)