본문으로 건너뛰기

© 2026 Molayo

Dev.to헤드라인2026. 05. 31. 20:57

MapWiki 구축하기: 개방형 협업 매핑 플랫폼에 대한 기술적 심층 분석

요약

MapWiki는 Wikipedia의 편집 모델과 OpenStreetMap의 공간 데이터를 결합한 TypeScript 기반의 협업형 지리 지식 플랫폼 MVP입니다. PostgreSQL/PostGIS를 활용하여 지리적 지식 그래프를 구축하며, 다양한 데이터 포맷 지원과 수정 이력 관리 기능을 제공합니다.

핵심 포인트

  • TypeScript 기반의 협업형 지리 지식 플랫폼 MVP 구축
  • PostgreSQL/PostGIS를 활용한 공간 데이터 영속성 확보
  • MapLibre를 이용한 대화형 지도 탐색 기능 제공
  • GeoJSON, KML 등 다양한 지리 데이터 포맷 입출력 지원
  • Next.js App Router 기반의 확장 가능한 아키텍처 설계

MapWiki는 협업형 지리 지식을 위한 TypeScript MVP (Minimum Viable Product, 최소 기능 제품)입니다. 제품의 아이디어는 간단합니다. Wikipedia의 사회적 편집 모델, OpenStreetMap의 공간적 깊이, 그리고 GitHub의 수정/감사 (revision/audit) 습관을 결합하는 것입니다.

대부분의 지도는 "그것이 어디에 있는가?"라는 질문에 답합니다. MapWiki는 "여기서 어떤 일이 일어났는가?", "여기에 무엇이 존재하는가?", 그리고 "여러 레이어 (layers)를 함께 볼 때 어떤 관계가 나타나는가?"라는 질문에 답할 수 있도록 설계되었습니다.

예를 들어, 사용자는 다음과 같은 항목들을 쌓아서 볼 수 있습니다:

  • AI 연구소 (AI Research Labs)
  • 대학교 (Universities)
  • 벤처 캐피털 기업 (Venture Capital Firms)
  • 반도체 팹 (Semiconductor Fabs)
  • 재생 에너지 프로젝트 (Renewable Energy Projects)

그 결과물은 단순한 지도가 아닙니다. 클러스터 (clusters), 의존성 (dependencies), 그리고 누락된 맥락을 드러낼 수 있는 지리적 지식 그래프 (geographic knowledge graph)입니다.

제품 범위 (Product Scope)

MVP에는 다음이 포함됩니다:

  • 지도 중심의 랜딩 페이지.
  • 대화형 MapLibre 탐색기.
  • 데이터셋 생성 마법사.
  • 점 (point), 선 (line), 다각형 (polygon) 객체.
  • 통계, 기여자, 댓글, 수정 이력 및 내보내기가 포함된 데이터셋 페이지.
  • CSV, TSV, GeoJSON, KML, GPX 가져오기 미리보기.
  • CSV, GeoJSON, JSON, KML, GPX 내보내기.
  • 순위가 지정된 글로벌 검색.
  • PostgreSQL/PostGIS 영속성 (persistence).
  • 추가 전용 (Append-only) 데이터셋 및 위치 수정 이력.
  • 중재 및 감사 테이블.
  • 필수 로그인의 대신 남용 제어 (abuse controls) 기능이 포함된 개방형 기여 모드.
  • Vercel 배포 설정.

아키텍처 (Architecture)

Architecture

두 가지 중요한 경계가 있습니다:

  1. UI는 데이터가 시드 데이터 (seed data)에서 오는지 Postgres에서 오는지 알지 못합니다.
  2. 라우트 핸들러 (Route handlers)는 가볍게 유지됩니다. 이들은 입력을 검증하고, 남용 제어를 적용하며, 리포지토리/서비스 (repositories/services)를 호출합니다.

이를 통해 MVP를 로컬에서 쉽게 실행할 수 있는 동시에, 프로덕션 환경에는 실제 데이터베이스 경로를 제공할 수 있습니다.

기술 스택 (The Stack)

계층 (Layer)선택 (Choice)이유 (Why)
앱 프레임워크 (App framework)Next.js App Router서버 렌더링된 페이지 (Server-rendered pages), API 라우트 핸들러 (API route handlers), Vercel 배포
.........

공간 데이터 모델 (Spatial Data Model)

MapWiki는 두 가지 핵심 엔티티 (Entity)를 중심으로 작동합니다:

  • 데이터셋 (Dataset): 커뮤니티에서 유지 관리하는 지도 레이어 (Map layer).
  • 위치 (Location): 기하학적 구조 (Geometry), 메타데이터 (Metadata), 출처 (Sources), 그리고 수정 이력 (Revisions)을 포함하는 데이터셋 내부의 객체.

데이터베이스는 모든 기하학적 구조를 SRID 4326을 사용하는 PostGIS에 저장합니다:

CREATE TABLE locations (
  id uuid PRIMARY KEY DEFAULT uuid_generate_v4(),
  dataset_id uuid NOT NULL REFERENCES datasets(id) ON DELETE CASCADE,
...

이를 통해 앱은 다음과 같은 기능에 필요한 기본 요소 (Primitives)를 확보합니다:

  • 뷰포트 쿼리 (Viewport queries).
  • 공간 필터링 (Spatial filtering).
  • 전체 텍스트 랭킹 (Full-text ranking).
  • JSON 메타데이터 필터 (JSON metadata filters).
  • 향후 벡터 타일 생성 (Vector tile generation).

수정 이력 (Revisions): 파괴적 편집 방지 (No Destructive Edits)

모든 편집은 복구 가능해야 합니다. 현재 행 (Row)은 최신 상태를 나타내며, 수정 이력 행은 부모 이력 (Parent history), 구조화된 차이점 (Structured diffs), 그리고 스냅샷 (Snapshots)을 저장합니다.

CREATE TABLE location_revisions (
  id uuid PRIMARY KEY DEFAULT uuid_generate_v4(),
  location_id uuid NOT NULL REFERENCES locations(id) ON DELETE CASCADE,
...

이전 버전을 복구하는 것은 또 다른 수정 이력을 생성해야 합니다. 이는 기존의 이력을 변형해서는 안 됩니다. 이는 Git이 조상 관계 (Ancestry)를 보존하는 방식과 Wikipedia가 편집 이력을 보존하는 방식과 유사합니다.

레이어 시스템 (Layer System)

가장 중요한 사용자 경험 (User experience)은 데이터셋을 쌓을 수 있는 능력입니다. 사용자는 각 데이터셋 레이어에 대해 활성화, 비활성화, 색상 변경 및 불투명도 (Opacity) 조절을 할 수 있습니다.

클라이언트 레이어 상태는 Zustand를 사용하며 로컬 스토리지 (Local storage)에 유지됩니다:

export type LayerSettings = {
  datasetId: string;
  name: string;
...

그러면 지도 탐색기 (Map explorer)는 활성화된 데이터셋을 도출하고 현재 지도 상태에 필요한 데이터만 요청할 수 있습니다:

curl "https://your-domain.example/api/locations?datasetIds=DATASET_ID&bbox=-125,24,-66,50&format=geojson"

MVP(Minimum Viable Product, 최소 기능 제품) 단계에서 앱은 GeoJSON을 반환합니다. 더 큰 규모로 확장될 경우, 상위 수준의 데이터셋 모델을 변경하지 않고도 리포지토리/API 경계가 벡터 타일 (Vector Tiles)로 진화할 수 있습니다.

임포트 파이프라인 (Import Pipeline)

커뮤니티 데이터셋은 종종 스프레드시트나 오픈 데이터 파일로 시작하기 때문에 임포트 (Import) 과정이 매우 중요합니다. MapWiki는 다음 형식을 지원합니다:

  • CSV
  • TSV
  • GeoJSON
  • KML
  • GPX

임포트 라우트 (Import route)는 파일을 커밋하기 전에 유효성을 검사하고 미리보기를 제공합니다.

const maxImportRows = 50_000;

function assertRowBudget(count: number) {
...

CSV 및 TSV 행은 일반적인 위도/경도 이름을 사용할 수 있습니다:

const latitude = asNumber(record.latitude ?? record.lat ?? record.Latitude ?? record.LAT);
const longitude = asNumber(record.longitude ?? record.lng ?? record.lon ?? record.Longitude ?? record.LON);

좌표가 없는 행은 조용히 삭제되는 대신, 향후 지오코딩 (Geocoding)을 위해 표시될 수 있습니다.

익스포트 파이프라인 (Export Pipeline)

익스포트 (Export)는 리포지토리 계층에서 동적으로 생성됩니다:

export function locationsToGeoJson(locations: Location[]): FeatureCollection {
  return toFeatureCollection(locations);
}

CSV 출력은 값을 이스케이프 (Escape) 처리하며, XML 형식은 KML 또는 GPX를 생성하기 전에 텍스트를 이스케이프 처리합니다:

function escapeXml(value: unknown) {
  return String(value ?? "")
    .replace(/&/g, "&")
...

필수 로그인 없는 개방형 기여

이 프로젝트는 인증을 강제하지 않고도 운영될 수 있습니다. 공개 쓰기 (Public writes) 작업은 익명의 기여자 행으로 귀속되며 남용 제어 (Abuse controls)에 의해 보호됩니다.

그러한 개방형 모델은 기여를 더 쉽게 만들지만, 다음과 같은 위험을 초래합니다:

  • 스팸 댓글.
  • 봇이 생성한 데이터셋.
  • 반복적인 중복 제출.
  • 대규모 요청 본문 (Request bodies).
  • 스크립트/HTML 페이로드 (Payloads).
  • API 스크래핑 (Scraping) 및 무차별 대입 임포트 시도.

해결책은 계층적 방어입니다.

남용 제어 (Abuse Controls)

입력 라우트는 다음을 사용합니다:

  • Postgres에 저장되는 고정 윈도우 속도 제한 (Fixed-window rate limits)
  • 라우트별 버스트 (Burst) 및 일일 정책
  • JSON 파싱 전 요청 본문(Request body) 바이트 제한
  • Zod 스키마 (Zod schemas)
  • 허니팟 필드 (Honeypot fields)
  • 스팸 문구 탐지 (Spam phrase detection)
  • URL 개수 제한
  • 중복 콘텐츠 지문 (Duplicate content fingerprints)
  • 스크립트/HTML 검사
  • 보이지 않는 문자 검사
  • 해시된 IP 차단 (Hashed IP bans)
  • 구조화된 남용 이벤트 로그

클라이언트 식별 정보는 프라이빗 솔트 (private salt)와 함께 해싱됩니다:

export function getClientIdentity(request: Request): ClientIdentity {
  const url = new URL(request.url);
  const ip = getClientIp(request);
...

활성 차단 (Active bans) 상태는 일반적인 라우트 작업이 수행되기 전에 확인됩니다:

const activeBan = await getActiveIpBan(identity);
if (activeBan) {
  return {
...

의심스러운 제출은 로그에 기록되며 일시적인 차단을 유발할 수 있습니다:

if (!ok || score >= 30) {
  await banClientIp(identity, {
    action: options.action,
...

이는 애플리케이션과 데이터베이스를 보호합니다. 대규모 볼륨의 DDoS 공격은 여전히 Vercel Firewall, 봇 보호(bot protection), 그리고 제공업체 수준의 방어 기제를 사용하여 엣지(edge)에서 처리되어야 합니다.

API 설계 (API Design)

MVP는 REST 스타일의 라우트 핸들러를 노출합니다:

엔드포인트 (Endpoint)목적 (Purpose)
GET /api/datasets데이터셋 목록 조회
...

OpenAPI 문서가 /api/openapi에서 제공되며, 다음 명령어로 정적 복사본을 생성할 수 있습니다:

npm run openapi

배포 (Deployment)

이 앱은 Vercel을 대상으로 설계되었습니다:

  • 서버 렌더링 페이지 및 API 라우트를 위한 Next.js App Router.
  • 프로덕션 데이터를 위한 PostGIS가 포함된 Neon Postgres.
  • 데이터베이스 및 보안 설정을 위한 Vercel 환경 변수.
  • 일일 크론 (cron) 상태 확인.

Vercel 설정은 의도적으로 최소화되어 있습니다:

{
  "framework": "nextjs",
  "regions": ["iad1"],
...

테스트 전략 (Testing Strategy)

MVP에는 다음이 포함됩니다:

  • 파서(parser)/익스포터(exporter)/남용 동작에 대한 단위 테스트 (Unit tests).
  • 리포지토리 (repositories)에 대한 통합 테스트 (Integration tests).
  • 중재 워크플로우 (Moderation workflow) 테스트.
  • 브라우저 동작에 대한 Playwright E2E (end-to-end) 커버리지.
  • TypeScript, ESLint 및 프로덕션 빌드 검사.

권장되는 머지 전 검사 (pre-merge checks):

npm run typecheck
npm run lint
npm run test
...

확장 경로 (Scaling Path)

MVP (Minimum Viable Product)는 다음 확장 단계에서 전체 재작성이 필요하지 않도록 설계되었습니다:

  1. ST_AsMVT를 사용한 벡터 타일 (vector tiles) 추가.
  2. 데이터셋 리비전 (revision)별 타일 캐시 무효화 (cache invalidation) 추가.
  3. 줌 레벨 (zoom level)별 지오메트리 단순화 (geometry simplification) 추가.
  4. 임포트 (import) 작업을 큐 (queue)로 이동.
  5. 트래픽이 높은 공개 레이어 (public layers)를 위한 데이터셋 구체화된 뷰 (materialized views) 추가.
  6. 읽기 API (read APIs)를 위한 엣지 캐싱 (edge caching) 추가.
  7. 더 강력한 Vercel 방화벽 (Firewall) 규칙 및 챌린지 플로우 (challenge flows) 추가.
  8. 데이터셋별 중재 정책 (moderation policies) 추가.

개발자가 다음에 구축할 수 있는 것들

권장되는 다음 기능들:

  • 스냅 (snapping) 및 실행 취소/다시 실행 (undo/redo) 기능이 포함된 시각적 지오메트리 에디터 (visual geometry editor).
  • 맵-디프 (map-diff) 렌더링을 포함한 리비전 비교 UI (revision compare UI).
  • 인용 품질 점수 산정 (citation quality scoring).
  • 공유 가능한 URL이 포함된 저장된 레이어 스택 (saved layer stacks).
  • 주소 전용 임포트를 위한 지오코딩 어댑터 (geocoding adapters).
  • 중재 큐 분류 필터 (moderation queue triage filters).
  • 벡터 타일 API 경로 (vector tile API route).
  • 공개 프로필 활동 피드 (public profile activity feeds).
  • 데이터셋 웹훅 (dataset webhooks).
  • 임베드 가능한 지도 (embeddable maps).

MapWiki는 단순히 지도 위젯이 포함된 CRUD 앱이 아닙니다. 어려운 부분은 경계(boundaries)에 있습니다: 공간 저장 (spatial storage), 협업 리비전 (collaborative revisions), 임포트/익스포트 (import/export), 공개 기여 (public contributions), 중재 (moderation), 그리고 남용 제어 (abuse controls)입니다. MVP는 이러한 경계들을 조기에 구축함으로써, 프로젝트가 샘플 데이터셋에서 시작하여 커뮤니티가 유지 관리하는 대규모 지리 지식 베이스로 성장할 수 있도록 합니다.

코드 및 기타 정보: https://www.dailybuild.xyz/project/149-mapwiki

AI 자동 생성 콘텐츠

본 콘텐츠는 Dev.to AI tag의 원문을 AI가 자동으로 요약·번역·분석한 것입니다. 원 저작권은 원저작자에게 있으며, 정확한 내용은 반드시 원문을 확인해 주세요.

원문 바로가기
0

댓글

0