레슨 6 / 8·20분
지리 데이터와 지도
D3로 지도 시각화하기
D3는 지리 데이터(GeoJSON, TopoJSON)를 기반으로 인터랙티브 지도를 만드는 강력한 기능을 제공합니다. 투영법(projection)을 사용하여 구체 위의 좌표를 2D 평면에 매핑하고, 다양한 시각적 효과를 적용할 수 있습니다.
GeoJSON 데이터 구조
GeoJSON은 지리 공간 데이터를 표현하는 표준 JSON 형식입니다. 각 feature는 geometry(점, 선, 다각형)와 properties(속성 데이터)로 구성됩니다.
javascript
// GeoJSON 구조 예시
var geojson = {
"type": "FeatureCollection",
"features": [
{
"type": "Feature",
"properties": {
"name": "서울특별시",
"population": 9700000,
"code": "11"
},
"geometry": {
"type": "Polygon",
"coordinates": [[[126.7, 37.4], [127.2, 37.4], /* ... */]]
}
},
// ... 다른 지역들
]
};투영법 (Projection)
투영법은 지구(3D)의 좌표를 화면(2D)의 좌표로 변환하는 방법입니다. D3는 수십 가지 투영법을 제공하며, 목적에 따라 적절한 것을 선택합니다.
javascript
var width = 600, height = 500;
// 메르카토르 투영법 (일반적인 웹 지도)
var projection = d3.geoMercator()
.center([127.5, 36.0]) // 대한민국 중심 좌표
.scale(5000) // 확대 비율
.translate([width / 2, height / 2]); // 화면 중앙에 배치
// 경로 생성기: 투영된 좌표를 SVG path로 변환
var pathGenerator = d3.geoPath()
.projection(projection);
var svg = d3.select('#map')
.append('svg')
.attr('width', width)
.attr('height', height);지도 그리기
javascript
// GeoJSON 데이터 로드 및 지도 그리기
d3.json('/data/korea.geojson').then(function(geoData) {
// 색상 스케일 (인구 기준)
var colorScale = d3.scaleQuantize()
.domain([0, 10000000])
.range(d3.schemeBlues[7]);
// 지역 경로 그리기
svg.selectAll('.region')
.data(geoData.features)
.join('path')
.attr('class', 'region')
.attr('d', pathGenerator)
.attr('fill', function(d) {
return colorScale(d.properties.population);
})
.attr('stroke', '#fff')
.attr('stroke-width', 0.5)
.on('mouseover', function(event, d) {
d3.select(this)
.transition()
.duration(200)
.attr('stroke-width', 2)
.attr('stroke', '#333');
tooltip.style('opacity', 1)
.html(
d.properties.name + '<br>' +
'인구: ' + d.properties.population.toLocaleString() + '명'
)
.style('left', (event.pageX + 15) + 'px')
.style('top', (event.pageY - 10) + 'px');
})
.on('mouseout', function() {
d3.select(this)
.transition()
.duration(200)
.attr('stroke-width', 0.5)
.attr('stroke', '#fff');
tooltip.style('opacity', 0);
});
// 범례 추가
var legend = svg.append('g')
.attr('transform', 'translate(20, 350)');
var legendScale = d3.scaleLinear()
.domain([0, 10000000])
.range([0, 200]);
var legendAxis = d3.axisBottom(legendScale)
.ticks(5)
.tickFormat(function(d) { return d / 10000 + '만'; });
legend.append('g')
.attr('transform', 'translate(0, 20)')
.call(legendAxis);
});주요 투영법 종류
- •d3.geoMercator() — 메르카토르 (웹 지도 표준, 고위도 왜곡)
- •d3.geoAlbers() — 알베르스 정적 원추 (미국 지도에 적합)
- •d3.geoEquirectangular() — 정거원통 (단순, 교육용)
- •d3.geoOrthographic() — 정사 투영 (지구본 느낌)
- •d3.geoNaturalEarth1() — 자연스러운 세계 지도
- •d3.geoConicEqualArea() — 정적 원추 (면적 보존)
줌과 패닝
javascript
// 줌/패닝 기능 추가
var zoom = d3.zoom()
.scaleExtent([1, 8]) // 줌 범위: 1배 ~ 8배
.on('zoom', function(event) {
svg.selectAll('path')
.attr('transform', event.transform);
});
svg.call(zoom);
// 특정 지역으로 줌 인
function zoomToRegion(feature) {
var bounds = pathGenerator.bounds(feature);
var dx = bounds[1][0] - bounds[0][0];
var dy = bounds[1][1] - bounds[0][1];
var x = (bounds[0][0] + bounds[1][0]) / 2;
var y = (bounds[0][1] + bounds[1][1]) / 2;
var scale = Math.max(1, Math.min(8, 0.9 / Math.max(dx / width, dy / height)));
var translate = [width / 2 - scale * x, height / 2 - scale * y];
svg.transition()
.duration(750)
.call(zoom.transform, d3.zoomIdentity
.translate(translate[0], translate[1])
.scale(scale));
}포인트 데이터 표시
javascript
// 도시 위치에 원형 마커 표시
var cities = [
{ name: '서울', lat: 37.5665, lon: 126.9780, value: 9700 },
{ name: '부산', lat: 35.1796, lon: 129.0756, value: 3400 },
{ name: '인천', lat: 37.4563, lon: 126.7052, value: 2900 },
{ name: '대구', lat: 35.8714, lon: 128.6014, value: 2400 },
{ name: '대전', lat: 36.3504, lon: 127.3845, value: 1500 },
];
var sizeScale = d3.scaleSqrt()
.domain([0, 10000])
.range([3, 20]);
svg.selectAll('.city')
.data(cities)
.join('circle')
.attr('class', 'city')
.attr('cx', function(d) { return projection([d.lon, d.lat])[0]; })
.attr('cy', function(d) { return projection([d.lon, d.lat])[1]; })
.attr('r', function(d) { return sizeScale(d.value); })
.attr('fill', 'rgba(231, 76, 60, 0.6)')
.attr('stroke', '#c0392b')
.attr('stroke-width', 1);💡
TopoJSON은 GeoJSON을 압축한 형식으로, 파일 크기가 훨씬 작습니다. topojson.feature()로 GeoJSON으로 변환하여 사용합니다. npm install topojson-client로 설치할 수 있습니다.