
그리는 구조
구조는 단순합니다.
- CustomPainter가 시군구 250개의 Path를 그립니다
- 그 위에 시도 경계선을 더 굵게 덧그립니다
- 전체를 InteractiveViewer로 감싸서 핀치 줌과 이동을 처리합니다
위경도를 화면 좌표로 바꾸는 투영은 직접 만들었습니다. 우리나라 정도 범위라면 복잡한 투영이 필요 없고, 경도에 cos(기준위도)를 곱해서 가로를 보정하는 정도면 충분합니다. 기준 위도는 35.9도로 잡았습니다.
lonMin 124.6 / lonMax 131.0 / latMin 33.1 / latMax 38.7 / latRef 35.9
scale = min(width / rawW, height / rawH)
처음에 밟은 지뢰들
선 굵기가 같이 커집니다. 확대하면 경계선도 같이 두꺼워져서 금방 지저분해집니다. painter가 TransformationController를 받아 strokeWidth를 현재 배율로 나누면 화면상 굵기가 일정하게 유지됩니다.
좌표를 두 번 변환하는 실수. InteractiveViewer 안쪽의 GestureDetector가 주는 localPosition은 이미 역변환이 끝난 좌표입니다. 여기에 직접 역행렬을 또 곱하면 엉뚱한 지역이 선택됩니다.
탭이 드래그 중에 발화합니다. 처음에 onTapDown으로 지역 선택을 처리했더니, 지도를 끌 때마다 지역 색이 토글됐습니다. 드래그로 판정되기 전에 먼저 불리기 때문입니다. onTapUp으로 바꿔서 해결했습니다.
16배로는 부족했습니다
확대 상한을 16배로 뒀는데, 작은 동네는 여전히 점처럼 보였습니다. 실제로 세어 보니 250개 중 112개 지역이 화면을 채우지 못했습니다. 가장 작은 부산 중구는 1배에서 약 1.6픽셀입니다.
그래서 160배로 올렸고, 나중에 900배까지 올렸습니다. 900배면 화면 폭이 실제 거리로 약 500m입니다. 이 정도 되면 20m 간격으로 단순화한 경계선이 각져 보이지만, 그건 예상된 현상이라 그냥 두기로 했습니다.
상한을 올리는 작업 자체는 상수 한 줄 수정이었습니다. InteractiveViewer.maxScale과 지역 포커스 계산이 모두 같은 상수를 참조하도록 처음부터 모아 둔 덕분입니다. 상수를 한 군데 모아 두면 나중에 이런 변경이 한 줄로 끝납니다.
지역명은 언제 보여줄까
확대하면 지역명을 띄우고 싶었는데, 기준을 정하는 게 고민이었습니다. 배율로 정하면 큰 지역과 작은 지역이 같은 취급을 받습니다.
그래서 지역마다 따로 판단하도록 했습니다. 그 지역이 화면에서 차지하는 폭이 화면 폭의 1/4 이상이면 이름을 띄웁니다. 경기도 어느 시는 조금만 확대해도 뜨고, 부산 중구는 한참 들어가야 뜹니다.
라벨 위치는 폴리곤 중심을 그냥 쓰면 안 됩니다. C자 모양 지역은 중심이 지역 바깥으로 나갑니다. mapshaper의 -points inner로 반드시 폴리곤 안쪽에 있는 점을 미리 뽑아 두고 그걸 씁니다(250개, 33KB).
겹침 처리는 화면에서 큰 지역부터 그리고, 이미 그린 라벨과 겹치면 건너뛰는 방식입니다.
화면 크기가 바뀌면 좌표계가 어긋납니다
검색으로 지역을 찾아 이동하면, 어떤 지역은 화면 중앙에 오고 어떤 지역은 위쪽으로 치우쳤습니다. 꽤 오래 헤맸는데 원인은 이것이었습니다.
- 투영(scale, offset)은 지도 영역 크기에 의존해서 계산됩니다
- 검색 화면이 닫히면서 하단 여백이 달라지면 지도 영역 높이가 바뀌고, 투영이 다시 계산됩니다
- 그런데 줌/이동 행렬은 옛 좌표계 기준으로 남아 있습니다
당장은 티가 덜 나는 수준이라 넘어갈까 하다가, 회전이나 다른 상황에서도 같은 문제가 날 게 뻔해서 근본적으로 고쳤습니다. 투영이 바뀌면 현재 행렬과 진행 중인 애니메이션을 새 좌표계로 보정하는 함수를 하나 두고, 투영 재계산 지점에서 반드시 거치게 했습니다.
이렇게 "지금은 문제가 안 되지만 구조적으로 틀린 것"을 그냥 두면, 나중에 전혀 다른 증상으로 돌아옵니다.
다음 편에서는
데이터 구조 이야기입니다. 공유 기능을 나중에 붙일 생각이라 처음부터 동기화를 염두에 두고 설계했고, 중간에 구조를 한 번 갈아엎었습니다.