지구가 아닌경우
- 때때로 지도는 지구 표면의 사물을 나타내지 않으며 이와같이 지리적 위도/경도에 대한 개념이 없는 경우가 있는데, 대부분 게임지도와 같은 경우이다.
- 이번 튜토리얼에서는 아래의 스타컨트롤2의 startmap 을 예제 지도이미지로 사용한다.
- 이 게임은 코너에서 볼 수 있듯이 사각 좌표계가 내장되어 있어 좌표계를 수립할 수 있다.
CRS.Simple
- CRS는 좌표 참조 시스템을 의미하며, 좌표 벡터에서 좌표가 무엇을 의미하는지 설명하기 위해 지리학자들이 사용하는 용어다.
- 예를 들어, [15, 60]은 지구에서 위도 경도를 사용할 경우 인도양의 한 지점을 나타내며, 또는 태양계 Krueger-Z는 우리의 starmap에서 볼 수 있다.
<!DOCTYPE html> | |
<html> | |
<head> | |
<title>CRS.Simple example - Leaflet</title> | |
<meta charset="utf-8" /> | |
<meta name="viewport" content="width=device-width, initial-scale=1.0"> | |
<link rel="stylesheet" href="https://unpkg.com/leaflet@1.7.1/dist/leaflet.css" integrity="sha512-xodZBNTC5n17Xt2atTPuE1HxjVMSvLVW9ocqUKLsCC5CXdbqCmblAshOMAS6/keqq/sMZMZ19scR4PsZChSR7A==" crossorigin=""/> | |
<script src="https://unpkg.com/leaflet@1.7.1/dist/leaflet.js" integrity="sha512-XQoYMqMTK8LvdxXYG3nZ448hOEQiglfqkJs1NOQV44cWnUrBc8PkAOcXy20w0vlaXaVUearIOBhiXZ5V3ynxwA==" crossorigin=""></script> | |
<style> | |
html, body { height: 100%; margin: 0; } | |
#map { width: 600px; height: 400px; } | |
</style> | |
</head> | |
<body> | |
<div id='map'></div> | |
<script> | |
/** | |
* Leaflet map은 CRS를 하나 가지고 있으며, map을 생성할때 변경이 가능하다. (CRS는 하나만 가질 수 있다) | |
* 이 게임맵 예제에서는 CRS.Simple 을 사용할 것이며, 사각형 그리드에 해당한다. | |
*/ | |
var map = L.map('map', { | |
crs: L.CRS.Simple | |
}); | |
/** | |
* 위 CRS 작업 후 (startmap)이미지와 대략적인 범위를 L.imageOverlay에 추가할 수 있다. | |
*/ | |
var bounds = [[0,0], [1000,1000]]; | |
var image = L.imageOverlay('uqm_map_full.png', bounds).addTo(map); | |
/** | |
* 전체지도를 표시한다. | |
* - 하지만 fitBounds를 실행하더라도 전체 지도가 보여지지 않는다. | |
*/ | |
map.fitBounds(bounds); | |
</script> | |
</body> | |
</html> |
CRS.Simple map의 일반적인 문제점
- Leaflet CRS의 기본값은 CRS.Earth이며, 360도의 경도는 256가로픽셀과 매핑되고 약170도 위도는 256세로픽셀과 매핑된다.(결국 정사각형이며, 줌 레벨 0인경우에 해당된다.)
- CRS에서 하나의 수평지도 단위는 하나의 수평픽셀에 매핑되며, 수직부분도 마찬가지다.
- map의 전체 부분은 1000x1000 픽셀크기이며, 따라서 우리의 HTML 컨테이너에 들어맞지 않는다.
- 다행히 minZoom 값을 0 이하로 설정할수 있다.
<!DOCTYPE html> | |
<html> | |
<head> | |
<title>CRS.Simple example - Leaflet</title> | |
<meta charset="utf-8" /> | |
<meta name="viewport" content="width=device-width, initial-scale=1.0"> | |
<link rel="stylesheet" href="https://unpkg.com/leaflet@1.7.1/dist/leaflet.css" integrity="sha512-xodZBNTC5n17Xt2atTPuE1HxjVMSvLVW9ocqUKLsCC5CXdbqCmblAshOMAS6/keqq/sMZMZ19scR4PsZChSR7A==" crossorigin=""/> | |
<script src="https://unpkg.com/leaflet@1.7.1/dist/leaflet.js" integrity="sha512-XQoYMqMTK8LvdxXYG3nZ448hOEQiglfqkJs1NOQV44cWnUrBc8PkAOcXy20w0vlaXaVUearIOBhiXZ5V3ynxwA==" crossorigin=""></script> | |
<style> | |
html, body { height: 100%; margin: 0; } | |
#map { width: 600px; height: 400px; } | |
</style> | |
</head> | |
<body> | |
<div id='map'></div> | |
<script> | |
var map = L.map('map', { | |
crs: L.CRS.Simple, | |
minZoom: -5 | |
}); | |
var bounds = [[0,0], [1000,1000]]; | |
var image = L.imageOverlay('uqm_map_full.png', bounds).addTo(map); | |
map.fitBounds(bounds); | |
</script> | |
</body> | |
</html> |
Pixels VS Map Units
- 한가지 일반적인 실수가 있는데, CRS.Simple를 사용할 때 'map units'와 'image pixels'와 같다고 가정하는 것이다.
- 이 경우에 지도는 1000x1000 단위(unit)를 커버하지만 이미지는 2315x2315픽셀이다.
- 상황에따라 '1 pixel = 1 map unit' 또는 '64 pixel = 1 map unit' 또는 다른 방법이 요구될 수 있다.
- 그리드의 map units에 맞춰서 레이어를 추가하는 식으로 생각해야 한다. (L.ImageOverlays, L.Markers 등등)
- 사실 우리가 사용하고 있는 이미지는 1000개의 map units을 커버하고 있다. (상당한 여백이 있다.)
- 0과 1000 사이에 몇개의 픽셀이 있는지 측정하고 추론하면 이 이미지에 대한 올바른 좌표 경계를 알 수 있다.
<!DOCTYPE html> | |
<html> | |
<head> | |
<title>CRS.Simple example - Leaflet</title> | |
<meta charset="utf-8" /> | |
<meta name="viewport" content="width=device-width, initial-scale=1.0"> | |
<link rel="stylesheet" href="https://unpkg.com/leaflet@1.7.1/dist/leaflet.css" integrity="sha512-xodZBNTC5n17Xt2atTPuE1HxjVMSvLVW9ocqUKLsCC5CXdbqCmblAshOMAS6/keqq/sMZMZ19scR4PsZChSR7A==" crossorigin=""/> | |
<script src="https://unpkg.com/leaflet@1.7.1/dist/leaflet.js" integrity="sha512-XQoYMqMTK8LvdxXYG3nZ448hOEQiglfqkJs1NOQV44cWnUrBc8PkAOcXy20w0vlaXaVUearIOBhiXZ5V3ynxwA==" crossorigin=""></script> | |
<style> | |
html, body { height: 100%; margin: 0; } | |
#map { width: 600px; height: 400px; } | |
</style> | |
</head> | |
<body> | |
<div id='map'></div> | |
<script> | |
var map = L.map('map', { | |
crs: L.CRS.Simple, | |
minZoom: -5 | |
}); | |
var bounds = [[-26.5,-25], [1021.5,1023]]; | |
var image = L.imageOverlay('uqm_map_full.png', bounds).addTo(map); | |
/** | |
* 태양계 위치에 마커를 추가 | |
*/ | |
var sol = L.latLng([ 145, 175.2 ]); | |
L.marker(sol).addTo(map); | |
map.setView( [70, 120], 1); | |
</script> | |
</body> | |
</html> |
이 LatLng는 당신이 알고있는 그것이 아니다.
- Sol의 좌표가 [175,145] 대신에 [145,175]로 되어있고, map center 또한 이렇게 되어있는것을 눈치 챘을 것이다.
- CRS.Simple의 좌표는 [x,y] 대신에 [y,x]를 사용하고 있으며 Leaflet 또한 [lng,lat] 대신에 [lat,lng]를 사용하고 있다.
- [lng,lat] or [lat,lng] or [y,x] or [x,y] 에 대한 논쟁은 새로운게 아니라 명확히 합의된게 없다.
- 또한 Leaflet의 L.Coordinate 이름 대신 L.LatLng 라는 클래스명을 사용한 것에 대해서도 합의가 부족하다.
- L.LatLng 라는 이름으로 [y,x] 좌표로 작업하는게 그다지 이해되지 않는다면 쉽게 래퍼를 만들어 사용할 수 있다.
<!DOCTYPE html> | |
<html> | |
<head> | |
<title>CRS.Simple example - Leaflet</title> | |
<meta charset="utf-8" /> | |
<meta name="viewport" content="width=device-width, initial-scale=1.0"> | |
<link rel="stylesheet" href="https://unpkg.com/leaflet@1.7.1/dist/leaflet.css" integrity="sha512-xodZBNTC5n17Xt2atTPuE1HxjVMSvLVW9ocqUKLsCC5CXdbqCmblAshOMAS6/keqq/sMZMZ19scR4PsZChSR7A==" crossorigin=""/> | |
<script src="https://unpkg.com/leaflet@1.7.1/dist/leaflet.js" integrity="sha512-XQoYMqMTK8LvdxXYG3nZ448hOEQiglfqkJs1NOQV44cWnUrBc8PkAOcXy20w0vlaXaVUearIOBhiXZ5V3ynxwA==" crossorigin=""></script> | |
<style> | |
html, body { height: 100%; margin: 0; } | |
#map { width: 600px; height: 400px; } | |
</style> | |
</head> | |
<body> | |
<div id='map'></div> | |
<script> | |
var map = L.map('map', { | |
crs: L.CRS.Simple, | |
minZoom: -3 | |
}); | |
/** | |
* L.LatLng 래퍼 xy | |
*/ | |
var yx = L.latLng; | |
var xy = function(x, y) { | |
if (L.Util.isArray(x)) { // When doing xy([x, y]); | |
return yx(x[1], x[0]); | |
} | |
return yx(y, x); // When doing xy(x, y); | |
}; | |
var bounds = [xy(-25, -26.5), xy(1023, 1021.5)]; | |
var image = L.imageOverlay('uqm_map_full.png', bounds).addTo(map); | |
/** | |
* 마커 추가 | |
*/ | |
var sol = xy(175.2, 145.0); | |
var mizar = xy( 41.6, 130.1); | |
var kruegerZ = xy( 13.4, 56.5); | |
var deneb = xy(218.7, 8.3); | |
L.marker( sol).addTo(map).bindPopup( 'Sol'); | |
L.marker( mizar).addTo(map).bindPopup( 'Mizar'); | |
L.marker(kruegerZ).addTo(map).bindPopup('Krueger-Z'); | |
L.marker( deneb).addTo(map).bindPopup( 'Deneb'); | |
/** | |
* (라인)Polyline 생성 후 지도에 추가 | |
*/ | |
var travel = L.polyline([sol, deneb]).addTo(map); | |
map.setView(xy(120, 70), 1); | |
</script> | |
</body> | |
</html> |
참고: https://leafletjs.com/examples/crs-simple/crs-simple.html
댓글
댓글 쓰기