Preview
데이터 준비하기
deck.gl의 Arc Layer Example을 우리나라 버전으로 변경하려고 한다.
https://deck.gl/examples/arc-layer
1. 우리나라 SHP 파일 다운로드
http://www.gisdeveloper.co.kr/?p=2332
2. 좌표계 변경 (WSG 84 - EPSG:4326)
https://norwegian-forest-cat.tistory.com/213
3. QGIS로 중심점 추출
- 도시별로 Arc를 연결하기 위해 중심점이 필요하다.
참고 : https://norwegian-forest-cat.tistory.com/215
code
File Tree
src
├── korea
│ ├── assets
│ │ └── arrow.svg
│ ├── maps
│ │ ├── centroid_geo.json
│ │ └── korea_wsg84_low.json
│ └── KoreaMap.jsx
├── App.jsx
└── index.js
assets / arrow.svg
- 화살표 SVG 파일
- maps / centroid_geo.json
중심점 및 Arc 목적지 데이터 파일
중심점 좌표 centroid_geo.json 파일 형식 Example
중심점 좌표에서 해당 도시를 클릭시 어떤 도시로 선이 뻗어나갈지 flows를 추가해줬다.
당연히 flows의 key값 (출발 index)가 중복되어서는 안된다.
원래 shp 파일에는 coordinates으로 되어있으나 centroid라는 이름을 쓰고 싶어 따로 추가했다.
{
"type": "FeatureCollection",
"features": [
{
"type": "Feature",
"geometry": {
"type": "Point",
"coordinates": [126.99182089982875, 37.551917718634975]
},
"properties": {
"CTPRVN_CD": "11",
"CTP_ENG_NM": "Seoul",
"CTP_KOR_NM": "서울특별시",
"centroid": [126.99182089982875, 37.551917718634975],
"flows": {
"1": 26, // 26번 코드(CTPRVN_CD)를 가진 도시로 화살표가 날아간다.
"2": 27,
"3": 29,
"4": 30,
"5": 45
}
}
},
{
"type": "Feature",
"geometry": {
"type": "Point",
"coordinates": [129.06056615289506, 35.20164910374823]
},
"properties": {
"CTPRVN_CD": "26",
"CTP_ENG_NM": "Busan",
"CTP_KOR_NM": "부산광역시",
"centroid": [129.06056615289506, 35.20164910374823],
"flows": {
"1": 44,
"2": 27,
"3": 29,
"4": 30,
"5": 45
}
}
},
}
maps / korea_wsg84_low.json
우리나라 지도 Layer 데이터 파일
KoreaMap.jsx
최대한 주석으로 설명
// KoreaMap.jsx
import React, { useState, useMemo } from "react";
import DeckGL from "@deck.gl/react";
import { GeoJsonLayer, ArcLayer, IconLayer } from "@deck.gl/layers";
import maplibregl from "maplibre-gl";
import { Map } from "react-map-gl";
import { scaleQuantile } from "d3-scale";
import KOREA_WSG84 from "./maps/korea_wsg84_low.json"; // 한국지도 Layer
import CENTROID_GEO from "./maps/centroid_geo.json"; // 도시별 중심점 및 화살표 좌표
import arrowIcon from "./assets/arrow.svg";
const inFlowColors = [
[255, 255, 204],
[199, 233, 180],
[127, 205, 187],
[65, 182, 196],
[29, 145, 192],
[34, 94, 168],
[12, 44, 132],
];
// const outFlowColors = [
// [255, 255, 178],
// [254, 217, 118],
// [254, 178, 76],
// [253, 141, 60],
// [252, 78, 42],
// [227, 26, 28],
// [177, 0, 38],
// ];
// Viewport settings, 초기 좌표는 서울시청으로 세팅
const INITIAL_VIEW_STATE = {
longitude: 126.9779451,
latitude: 37.5662952,
zoom: 6, // 숫자가 높을수록 확대됨
pitch: 30, // 상하로 기울어진 각도, 높을수록 기울어진 상태에서 start
bearing: 0, // 좌우로 꺾이는 각도, 90이면 90도 꺾임
};
// Arc 화살표의 목적지를 계산하는 함수
function calculateArcs(data, selectedCounty) {
if (!data || !data.length) {
return null;
}
// 초기 좌표를 서울특별시로 setting
if (!selectedCounty) {
selectedCounty = data.find((f) => f.properties.CTP_KOR_NM === "서울특별시");
}
const centroidData = CENTROID_GEO.features.find(
(f) => f.properties.CTP_KOR_NM === selectedCounty.properties.CTP_KOR_NM
);
const { flows, centroid } = centroidData.properties;
const arcs = Object.keys(flows).map((toId) => {
// !원본!
// const f = data[toId];
const f = data.find(
(obj) => parseInt(obj.properties.CTPRVN_CD) === flows[toId]
);
return {
source: centroid, // source: 출발점 좌표
target: f.properties.centroid, // target: 목적지 좌표
value: flows[toId], // flows: 각 도시의 CTPRVN_CD ID
};
});
const scale = scaleQuantile()
.domain(arcs.map((a) => Math.abs(a.value)))
.range(inFlowColors.map((c, i) => i));
arcs.forEach((a) => {
console.log(a);
a.gain = Math.sign(a.value);
a.quantile = scale(Math.abs(a.value));
});
return arcs;
}
// hover시 이름 표시됨
// 마우스가 Arc 선에 닿으면 오류가 생겨, try catch로 오류를 발생하지 않고 지나가게 했다.
function getTooltip({ object }) {
try {
return object && object.properties.CTP_KOR_NM;
} catch (err) {
if (err === TypeError) {
console.log(err);
}
}
// return object && object.properties.CTP_KOR_NM;
}
export default function KoreaMap() {
const data = CENTROID_GEO.features;
const [selectedCounty, selectCounty] = useState(null);
const arcs = useMemo(
() => calculateArcs(data, selectedCounty),
[data, selectedCounty]
// console.log(data)
);
// 세계지도 Base Map
// deck.gl 사이트에서 Base Map 선택 가능 : https://deck.gl/docs/api-reference/carto/basemap
const MAP_STYLE =
"https://basemaps.cartocdn.com/gl/dark-matter-gl-style/style.json";
// 한국 지도 Layer
const koreaWsg84 = new GeoJsonLayer({
id: "korea-map",
data: KOREA_WSG84,
// Styles
filled: true,
pointRadiusMinPixels: 2,
pointRadiusScale: 2000,
getPointRadius: (f) => 11 - f.properties.scalerank,
getFillColor: [246, 241, 241, 180], // [r, g, b, a(Alpha, 투명도 0 ~ 255)]
getLineWidth: 600,
getLineColor: [25, 108, 148, 80],
// Interactive props
pickable: true,
autoHighlight: true,
onClick: ({ object }) => {
selectCounty(object);
},
});
const arcsLayer = new ArcLayer({
id: "arc",
data: arcs,
getSourcePosition: (d) => d.source,
getTargetPosition: (d) => d.target,
// Arc 라인 색상 후보1
// getSourceColor: [50, 89, 217],
// getTargetColor: [194, 52, 155],
// Arc 라인 색상 후보2
// getSourceColor: [107, 182, 255],
// getTargetColor: [1, 75, 179],
// Arc 라인 색상 후보3
getSourceColor: [47, 229, 158],
getTargetColor: [77, 75, 201],
// getSourceColor: (d) =>
// (d.gain > 0 ? inFlowColors : outFlowColors)[d.quantile],
// getTargetColor: (d) =>
// (d.gain > 0 ? outFlowColors : inFlowColors)[d.quantile],
getWidth: 5, // 화살표 두께
greatCircle: true,
onIconError: true,
});
// Arc 선 끝 화살표 SVG 위치 조정 (svg size: 70*70)
const ICON_MAPPING = {
marker: { x: 0, y: 5, width: 70, height: 100, mask: true },
};
const iconLayer = new IconLayer({
id: "icon-layer",
data: arcs,
pickable: true,
iconAtlas: arrowIcon,
iconMapping: ICON_MAPPING,
getIcon: (d) => "marker",
sizeScale: 10,
// getPosition: (d) => d.properties.centroid,
getPosition: (d) => d.target,
getSize: (d) => 3,
// getColor: (d) => [Math.sqrt(d.exits), 140, 0],
getColor: (d) => [77, 75, 201],
});
// 레이어 추가
const layers = [koreaWsg84, arcsLayer, iconLayer];
return (
<DeckGL
initialViewState={INITIAL_VIEW_STATE}
controller={{ scrollZoom: { speed: 0.1, smooth: true } }} // drag 옵션
// 드래그 막기 옵션(공식문서에 보면 객체로 넣어야 한다)
// controller={{ scrollZoom: false }}
layers={layers}
getTooltip={getTooltip}
>
<Map
reuseMaps
mapLib={maplibregl}
mapStyle={MAP_STYLE}
preventStyleDiffing={true}
/>
</DeckGL>
);
}
'시각화' 카테고리의 다른 글
[Openlayers] 지도 기본 컨트롤러 숨기기 (0) | 2024.06.13 |
---|---|
[OpenLayers] 지도 확대 축소시 겹치는 text 안보이게 하기 - Declutter (0) | 2023.10.24 |
[Openlayers] hover시 원하는 레이어에 선택하여 속성 표출하기(pointermove) (0) | 2023.08.03 |
[HTML] html 문서 안에 는 무엇인가? - 특수문자 (0) | 2023.04.11 |
[Echarts] SVG 게이지 그래프 만들기 (0) | 2023.04.04 |