해당 게시물에서는 지도 API인 Leaflet을 활용하여, 기준 중심좌표로부터 일정한 반경으로 둘러싸는 사각형을 그리는 방법에 대해 정리해보았다.
목차
결과 예시
간단하게 codePen을 활용하여 표출을 진행해보았다.
See the Pen 특정좌표의 반경 polygon by HamJeong (@HamJeong) on CodePen.
html코드와 css코드는 특별한 구간이 없기 때문에 javascript 코드를 첨부해보았다(가독성과 추가 설명을 위해 일부 첨삭함)
var domainAddr = 'https://tiles.osm.kr'; //map layer 도메인
const center=L.latLng(37.44766071, 126.45232931); //중심좌표
// 미터 단위로 거리 계산을 위한 사각형 크기
const widthInMeters = 1000; //m
const heightInMeters = 1500; //m
let currentLine = null; // 클릭된 선 객체를 저장할 변수 (기존 선 삭제용)
// 지도 객체 생성
map = L.map('map', {
center:center,
attributionControl:false,
keyboard: false,
tap: false,
doubleClickZoom:false,
touchZoom: false,
zoom: 12,
controls: [],
layers:[
L.tileLayer(domainAddr + '/hot/{z}/{x}/{y}.png',
{opacity: 0.6, maxZoom: 18, minZoom: 2,attribution: '© 2019. AMO, <a href="http://osm.org/copyright">OpenStreetMap</a> contributors'}),
],
});
L.marker(center).addTo(map).bindPopup("중심 좌표").openPopup();
const bounds = [ // 중심 좌표에서 미터 단위로 계산된 모서리 좌표
map.options.crs.unproject(
map.options.crs.project(center).subtract([widthInMeters / 2, heightInMeters / 2])
),
map.options.crs.unproject(
map.options.crs.project(center).add([widthInMeters / 2, heightInMeters / 2])
),
];
// 사각형 생성 및 지도에 추가
const rectangle = L.rectangle(bounds, { color: 'blue', weight: 1 });
rectangle.addTo(map);
// 거리 측정을 위한 클릭 이벤트 설정
rectangle.on('click', function (event) {
const clickedLatLng = event.latlng;// 클릭된 좌표
const distance = center.distanceTo(clickedLatLng);// 중심 좌표~클릭된 지점 거리
if (currentLine) { // 이전 클릭된 선 제거
map.removeLayer(currentLine);
}
// 중심 좌표와 클릭된 좌표를 연결하는 선
currentLine = L.polyline([center, clickedLatLng], { color: 'red', weight: 2 }).addTo(map);
// 거리 정보 팝업 표시
L.popup()
.setLatLng(clickedLatLng)
.setContent(`거리: ${distance.toFixed(2)} meters`)
.openOn(map);
});
위 js 순정상태 소스의 result를 확인해보면 아래와 같다. 분명히 해당 사각형의 가로길이는 1km반경으로 설정했기 때문에 중심좌표로부터 직선에 위치한 양측 변에 포인트를 찍으면 거리는 500m근사값으로 조회되어야하는데 너무 터무니 없는 값이 나왔다.
위 문제 원인은 해당 지도 API의 기준 좌표계가 EPSG:3857인 것으로 부터 발생한다.
해당 좌표 투영 방식의 특성 상, 위도에 따라 동/서 방향 거리가 왜곡되기 때문에 위도가 높아질수록 경도 방향에 거리가 짧아지는 상태인 것으로 정리할 수있다.
예시코드 개선
언급된 거리 왜곡을 보정하기 위해서 두개의 개체 사이 거리를 반환하는 Leaflet의 L.latLng.distanceTo()를 활용해보았다.
var domainAddr = 'https://tiles.osm.kr'; //map layer 도메인
const center=L.latLng(37.44766071, 126.45232931); //중심좌표
// 미터 단위로 거리 계산을 위한 사각형 크기
const widthInMeters = 1000; //m
const heightInMeters = 1500; //m
let currentLine = null; // 클릭된 선 객체를 저장할 변수 (기존 선 삭제용)
// 지도 객체 생성
map = L.map('map', {
center:center,
attributionControl:false,
keyboard: false,
tap: false,
doubleClickZoom:false,
touchZoom: false,
zoom: 12,
controls: [],
layers:[
L.tileLayer(domainAddr + '/hot/{z}/{x}/{y}.png',
{opacity: 0.6, maxZoom: 18, minZoom: 2,attribution: '© 2019. AMO, <a href="http://osm.org/copyright">OpenStreetMap</a> contributors'}),
],
});
L.marker(center).addTo(map).bindPopup("중심 좌표").openPopup();
// 동서 방향 거리 보정
const halfWidthDegrees = (widthInMeters / 2) / (40075000 * Math.cos(center.lat * Math.PI / 180) / 360);
// 중심에서 보정된 좌표 구하기
const bounds = [
L.latLng(center.lat - (heightInMeters / 2) / 111320, center.lng - halfWidthDegrees), // 좌하단
L.latLng(center.lat + (heightInMeters / 2) / 111320, center.lng + halfWidthDegrees) // 우상단
];
// 사각형 생성 및 지도에 추가
const rectangle = L.rectangle(bounds, { color: 'blue', weight: 1 });
rectangle.addTo(map);
// 거리 측정을 위한 클릭 이벤트 설정
rectangle.on('click', function (event) {
const clickedLatLng = event.latlng;// 클릭된 좌표
const distance = center.distanceTo(clickedLatLng);// 중심 좌표~클릭된 지점 거리
if (currentLine) { // 이전 클릭된 선 제거
map.removeLayer(currentLine);
}
// 중심 좌표와 클릭된 좌표를 연결하는 선
currentLine = L.polyline([center, clickedLatLng], { color: 'red', weight: 2 }).addTo(map);
// 거리 정보 팝업 표시
L.popup()
.setLatLng(clickedLatLng)
.setContent(`거리: ${distance.toFixed(2)} meters`)
.openOn(map);
});
앞서 공유된 코드에서 halfWidthDegrees변수의 할당을 추가하고, bounds변수 값을 수정하였다.
직선이 아닌건 조금 감안해주셔요 ^_^...
약간 보정되어 500m근사값이 조회되기는 하나 100% 정확하지는 않는 것으로 보인다.
지구 곡률과 투영 방식 한계로 인해 보정을 하더라도 약간의 오차가 발생하는 것은 감안해야한다는 것이다.
본인의 경우 이정도의 근사값이라면 크게 문제가 없기 때문에 추가적인 보정 작업을 진행하지는 않았으나, 추가로 개선할 수 있는 방법을 확인하고 참고할 수 있는 내용을 아래 목차에 첨부해 보았다.
왜곡 값의 완벽한 보정
완벽한 보정은 가능한가?
결과부터 이야기 하자면, 계속 언급하고 있지만 ESPS:3857 투영의 한계로 인해 중심 좌표로부터 사각형 변까지의 모든 직선 거리를 균일하게 만드는 것은 어려울 수 있다.
하지만 제시되는 언급되는 3가지 방법으로 위의 예시 결과보다 더 보정된 값으로 오차를 줄일 수는 있다.
1. 직접 거리 측정해서 그리기 ^^
당연한 소리이지만 L.latLng.distanceTo()를 각 변에 대해 직접 측정하여 오차를 확인하고, 보정 값을 추가로 적용하는 방식도 가능하다.
2. EPSG:4326 사용
Leaflet의 기본 좌표계인 ESPS:3857에서, EPSG:4326(WGS84 좌표계)을 사용하는 방식으로 변경하면 지도에서의 거리를 계산할 때 지구 타원체 기반으로 처리할 수 있다.
다만, Leaflet의 기본 설정과 호환성이 떨어질 수 있는 부분은 감안해야한다.
3. 지구 곡률 기반 거리 계산
드디어 그나마 답변같은 답변을 제시한다. 이러한 문제들을 개선하기 위해 작업된 플러그인을 활용하는 방법이다. 예시로, Geodesic플러그인을 활용하는 방법이 있다.
// Geodesic 플러그인을 사용해 사각형 그리기 예시
L.geodesic([[
center,
L.latLng(center.lat, center.lng + (500 / 111320)), // 동쪽 경계
L.latLng(center.lat + (750 / 111320), center.lng), // 북쪽 경계
L.latLng(center.lat, center.lng - (500 / 111320)), // 서쪽 경계
L.latLng(center.lat - (750 / 111320), center.lng) // 남쪽 경계
]], {
weight: 2,
color: 'blue'
}).addTo(map);
혹여 추가로 특정 좌표 반경의 원형을 그리고싶은 사람이 있을까 싶어, Leaflet과 유사한 지도 API인 OpenLayers로 해당 예시를 구현하는 방법이 정리된 블로그 포스팅을 참조하였다.
Leaflet과 OpenLayers는 크게 다르지 않기 때문에, 해당 포스팅에서 작업 내용을 참고하여 개발자 문서를 통해 Leaflet에서도 충분히 구현할 수 있을거라 생각한다.https://androman.tistory.com/205#google_vignette
'JavaScript' 카테고리의 다른 글
[JS] 타임 슬라이더 구현(자동 스크롤) (0) | 2024.11.25 |
---|---|
[지도 API] leaflet (0) | 2024.11.22 |
[JS] 부등식 검증 함수 (0) | 2024.07.04 |
Object의 value로 index접근하는 방법 (0) | 2022.11.24 |
[openLayers_proj] 좌표계 변환 (0) | 2022.11.21 |