💡 이 글에서는 아래의 기술을 사용합니다!
지도 : Mapbox - Deck.gl과 호환이 좋은 지도 SDK
레이어 : Deck.gl - WebGL2를 사용하여 대규모 데이터 시각화에 최적화된 라이브러리
타일 : OpenStreetMap(OSM)💡 이 글을 통해 다음의 내용을 알 수 있습니다!
- 좌표로부터 렌더링할 지도의 타일을 불러올 수 있습니다.
- 지도의 타일으로부터 색상을 추출할 수 있습니다.
- 추출한 색상으로부터 대비되는 색상을 추출할 수 있습니다.
현재 진행중인 프로젝트에서 공간 데이터를 지도 위에 표시하는데, 여러 타입의 지도에서도 잘 보이는 색으로 표시해야하는 요구사항이 있어서, 이를 해결하기 위해 고민한 내용과 해결방법을 공유하고자 글을 작성했습니다!
다음은 Mapbox에서 기본적으로 제공해주는 한국 지도 데이터와 지도 타일을 사용한 지도입니다.
하지만, 이는 15레벨 이상으로 확대하면 해당 아티클에서는 불필요한 빌딩 데이터가 렌더링되고, 지도의 정확도 또한 OSM이나 타 한국의 지도보다 떨어지는 편이니 Mapbox의 레이어와 호환되는 부분만을 사용하도록 하고, Mapbox에서 기본적으로 제공되는 tile과 building 데이터는 사용하지 않도록 하겠습니다.
먼저, Mapbox Studio에서 기본 타일을 grayscale하고, 빌딩 데이터와 같은 불필요한 정보들을 삭제한 후 로드했습니다.
하지만, 앞서 기술했듯 Mapbox의 타일은 정확도가 떨어지는 편이니 비교적 정확도가 높은 OSM의 타일을 사용해보겠습니다.
간단하게 좌표를 지정하여 하늘색 마커를 띄워보겠습니다.
위와 같이 zoom이 낮은 부분에서는 마커가 비교적 잘 보이는 편입니다.
하지만, 이를 확대하면 아래와 같이 오른쪽 하천 부분의 마커가 보이지 않습니다.
(물론 해당 마커는 극적인 효과를 위해 강의 색상과 동일한 색상을 사용했기 때문에 잘 안보이는 경우가 맞습니다.)
하지만, 아래와 같은 상황을 고려해야합니다.
정리하자면,
- 마커는 위치가 정해져있지 않고, 변경될 수 있다
- 마커의 위치가 변함에 따라 마커 아래 타일의 색상 분포가 달라진다
- 타일의 종류가 다양할 수 있다.
의 문제점이 있다고 할 수 있습니다.
이를 해결하기 위해 저는 다음과 같은 방법을 제시했습니다.
(문제점을 해결하기 위해 시도한 방법중 하나를 공유하는 것일 뿐 정답이 아닐 수 있습니다!)
- 좌표로부터 특정 확대 레벨에서의 타일을 가져옵니다
- 타일의 색상 분포를 추출합니다.
- 추출한 색상과 대비되는 색으로 마커의 색을 결정합니다.
해당 글에서는 1의 과정을 풀어내고, 2와 3의 과정은 다음 글에서 이어서 작성하겠습니다.
해당 작업은 비교적(?) 어렵지 않게 작업할 수 있었습니다.
Open Street Map Wiki의 Slippy map tilenames문서에서 자세히 설명된 과정을 따라가며 어렵지 않게 구현하 수 있었습니다….만… 중간에 위키의 잘못된(이라 생각되는) 부분이 있어서 그렇게 생각하는 이유와 구현 과정을 풀어보겠습니다.
+) 해당 문서를 공부하면서 한글 문서로도 번역을 해놓았으니, 한글문서로 읽으셔도 무방합니다.
미끄러운 지도(Slippy Map : Static Map은 움직이지 않는 지도로, 이와 반대되는 지도)에서는 타일에 대해 다음과 같이 정의합니다.
- 타일은 256 × 256 픽셀 PNG 파일입니다.
- 각 확대 레벨은 디렉터리이고, 각 열은 하위 디렉터리이며, 그 열의 각 타일은 파일입니다.
- 파일 이름(또는 URL) 형식은
/zoom/x/y.png
입니다.
첫번째 줄은 명백하니 넘어가고,
두번째 줄은 우리가 시스템에서 파일 경로를 /User/moonki/Desktop/...
과 같이 작성할 수 있듯이, 해당 타일이 위치한 경로를 zoom - x - y 의 구조로 저장하고 있다는 것을 알 수 있습니다.
또한 이 구조는 파일경로를 검색할때와 동일하게 /zoom/x/y.png
으로 접근할 수 있습니다.
이때 쓰이는 x, y의 좌표를 구하기 위해 문서의 내용을 따라가보겠습니다.
타일은 지도 서비스를 제공하는 여러 업체에서 url의 형식으로 에셋을 접근할 수 있습니다.
여러 제공업체에서 이를 제공하고 있지만, 저작권의 이유로 사용하지 못하는 경우도 있으니 알아보고 사용하셔야 합니다.
이를 위해 저는 OSM의 tile을 사용하도록 하겠습니다.
🗺️ ~://tile.openstreetmap.org/zoom/x/y.png
확대 레벨은 해당 아티클에서는 16의 확대 레벨으로 두고 사용하겠습니다
🗺️ ~://tile.openstreetmap.org/16/x/y.png
해당 좌표 타일의 위치 x, y값을 가져오기 위해 현재 좌표로부터 타일의 위치를 유도하는 과정을 유도해보겠습니다.
예시를 위해 아래의 두 좌표를 담은 mockData를 사용하겠습니다.
mockData = [
{
id: 0,
position: [127.10501086, 37.35887931],
},
{
id: 1,
position: [127.10881588, 37.3613688],
},
]
데이터는 위도-경도 좌표계로 알려져있는 EPSG:4326(WGS 84)의 형식을 따르고 있습니다.
이 중 첫번째 position을 위키의 내용을 따라서 EPSG:3857의 형식으로 변환해보겠습니다.
위키의 예시에서는 EPSG:3857의 format으로 변환한 데이터가
x: lat을 그대로 따름
y: (-π, π)의 범위로 transform
이 되어야 하지만, 실제 EPSG translator를 사용하여 변환한 결과 x, y의 값이 터무니 없이 커져버렸습니다.
이해가 되지 않는 부분이 있어 OSM 커뮤니티에 질문을 남긴 결과 다음과 같은 대답을 얻을 수 있었습니다
요약하자면, 실제 EPSG:3857으로 표시되어있기는 하지만, 실제 값을 사용하지 않고 EPSG:3857을 변환할때 사용하는 방법을 차용한 것이므로, 이름을 EPSG:3857으로 정한것에는 문제가 있다(라고 생각한다) 라고 합니다.
이에 EPSG:3857을 따르지 않고, 수학 공식을 직접 변환하여 다음과 같은 식을 도출해낼 수 있습니다.
const tileX = Math.floor((numTile * (targetCoordX + 180)) / 360);
const tileY = Math.floor((numTile / 2) * (1 - Math.log(
Math.tan((Math.PI / 180) * targetCoordY) +
1 / Math.cos((Math.PI / 180) * targetCoordY)
) / Math.PI)
);
조금은 식이 복잡하지만, tileX와 tileY를 도출해내는 수학 공식을 각각 한줄의 코드로 간단하게 나타낸 부분입니다.
위의 코드를 사용하면 각각의 좌표에 대해서 다음과 같은 결과를 얻을 수 있습니다.
→ 좌표 1
before : [127.10501086, 37.35887931]
after : [55906, 25426]
https://tile.openstreetmap.org/16/55906/25426.png
→ 좌표 2
before : [127.10881588, 37.3613688]
after : [55907, 25425]
https://tile.openstreetmap.org/16/55907/25425.png
이렇게 위키의 내용을 따라 위도-경도 좌표계에서 지도 타일에서 쓰는 좌표계로의 변환 이후 타일의 위치를 알아내는 방법에 대해 작성해보았습니다.
이후의 이어지는 아티클에서는 위에서 얻은 지도 사진에서 k-means clustering을 통해 유효한 대표 색상값을 추출해내고, 해당 색상값들의 조합에서 잘 분리되어 보이는 색상을 골라내는 방법에 대해서 다루어보도록 하겠습니다
참고자료
https://developers.naver.com/forum/posts/27554
https://wiki.openstreetmap.org/wiki/Slippy_map_tilenames
https://wiki.openstreetmap.org/wiki/Ko:Slippy_map_tilenames
https://community.openstreetmap.org/t/clarification-needed-on-epsg-3857-coordinate-conversion-in-slippy-map-wiki/116242