iBetter Books
수정

Ch 03. leaflet으로 지도 시각화

숫자만으로 설명하기 어려운 지역 데이터는 지도에 올리면 한눈에 보입니다. leaflet은 OpenStreetMap 기반의 인터랙티브 지도 라이브러리로, R의 leaflet 패키지를 통해 Shiny 앱에 드래그·줌·팝업을 갖춘 지도를 손쉽게 삽입할 수 있습니다.

leaflet 설치

install.packages("leaflet")

기본 지도 만들기

leaflet()으로 지도 객체를 만들고, 파이프(%>%)로 레이어를 쌓습니다.

library(shiny)
library(leaflet)

ui <- fluidPage(
  titlePanel("기본 지도"),
  leafletOutput("map", height = "500px")  # leafletOutput으로 자리 확보
)

server <- function(input, output, session) {
  output$map <- renderLeaflet({  # renderLeaflet으로 렌더링
    leaflet() %>%
      addTiles() %>%            # OpenStreetMap 타일 추가
      setView(
        lng  = 126.978,         # 서울 경도
        lat  = 37.566,          # 서울 위도
        zoom = 11               # 줌 레벨 (1~18)
      )
  })
}

shinyApp(ui, server)

addTiles()는 기본 OpenStreetMap 타일을 불러옵니다. 다른 지도 스타일로 바꾸려면 addProviderTiles()를 씁니다.

leaflet() %>%
  addProviderTiles(providers$CartoDB.Positron)   # 밝은 톤의 심플 지도
  # providers$CartoDB.DarkMatter                 # 다크 모드 지도
  # providers$Esri.WorldImagery                  # 위성 사진
  # providers$OpenTopoMap                        # 지형도

addMarkers: 마커 추가

library(shiny)
library(leaflet)

# 주요 도시 데이터
cities <- data.frame(
  name = c("서울", "부산", "대구", "인천", "광주", "대전"),
  lat  = c(37.566, 35.179, 35.871, 37.456, 35.160, 36.351),
  lng  = c(126.978, 129.076, 128.601, 126.705, 126.851, 127.385),
  pop  = c(9736962, 3404423, 2418346, 2948375, 1441970, 1471040),
  stringsAsFactors = FALSE
)

ui <- fluidPage(
  leafletOutput("city_map", height = "500px")
)

server <- function(input, output, session) {
  output$city_map <- renderLeaflet({
    leaflet(cities) %>%
      addTiles() %>%
      setView(lng = 127.9, lat = 36.5, zoom = 7) %>%

      addMarkers(
        lng   = ~lng,
        lat   = ~lat,
        popup = ~paste0(
          "<b>", name, "</b><br>",
          "인구: ", format(pop, big.mark = ","), "명"
        ),
        label = ~name   # 마우스 호버 시 표시
      )
  })
}

shinyApp(ui, server)

popup은 클릭 시 나타나는 말풍선이고, label은 마우스를 올렸을 때 나타나는 툴팁입니다. 둘 다 HTML을 지원합니다.

addCircleMarkers: 원형 마커

값의 크기를 원의 반지름으로 표현할 때 씁니다. 인구, 매출, 사고 건수 같은 양적 변수를 시각적으로 비교할 수 있습니다.

server <- function(input, output, session) {
  output$city_map <- renderLeaflet({
    leaflet(cities) %>%
      addProviderTiles(providers$CartoDB.Positron) %>%
      setView(lng = 127.9, lat = 36.5, zoom = 7) %>%

      addCircleMarkers(
        lng         = ~lng,
        lat         = ~lat,
        radius      = ~sqrt(pop / 100000),  # 인구에 비례한 반지름
        color       = "#2c3e50",
        weight      = 1,
        fillColor   = "#3498db",
        fillOpacity = 0.7,
        popup       = ~paste0("<b>", name, "</b><br>인구: ",
                               format(pop, big.mark = ","), "명"),
        label       = ~name
      )
  })
}

색상 팔레트로 값 시각화

colorNumeric(), colorFactor(), colorBin(), colorQuantile()로 색상 팔레트를 만들어 마커나 폴리곤에 적용합니다.

library(shiny)
library(leaflet)

ui <- fluidPage(
  selectInput("palette", "색상 팔레트",
              choices = c("YlOrRd", "Blues", "Greens", "RdBu")),
  leafletOutput("choropleth_map", height = "500px")
)

server <- function(input, output, session) {
  output$choropleth_map <- renderLeaflet({
    # 임의 데이터 생성 (실제로는 sf/sp 폴리곤 데이터 사용)
    cities_with_value <- cities
    cities_with_value$value <- cities_with_value$pop / 1e6

    pal <- colorNumeric(
      palette = input$palette,
      domain  = cities_with_value$value
    )

    leaflet(cities_with_value) %>%
      addProviderTiles(providers$CartoDB.Positron) %>%
      setView(lng = 127.9, lat = 36.5, zoom = 7) %>%
      addCircleMarkers(
        lng         = ~lng,
        lat         = ~lat,
        radius      = 15,
        fillColor   = ~pal(value),
        fillOpacity = 0.8,
        color       = "white",
        weight      = 2,
        popup       = ~paste0(name, ": ", round(value, 2), "백만 명")
      ) %>%
      addLegend(
        position = "bottomright",
        pal      = pal,
        values   = ~value,
        title    = "인구(백만)",
        opacity  = 0.8
      )
  })
}

shinyApp(ui, server)

addLegend()로 색상 범례를 추가합니다. position"bottomright", "bottomleft", "topright", "topleft" 중 하나입니다.

leafletProxy: 지도를 다시 그리지 않고 업데이트

입력 값이 바뀔 때마다 renderLeaflet()이 실행되면 지도가 처음부터 다시 로드됩니다. leafletProxy()를 쓰면 마커만 교체하거나 뷰만 이동할 수 있어 훨씬 자연스럽습니다.

library(shiny)
library(leaflet)

cities <- data.frame(
  name   = c("서울", "부산", "대구", "인천"),
  lat    = c(37.566, 35.179, 35.871, 37.456),
  lng    = c(126.978, 129.076, 128.601, 126.705),
  region = c("수도권", "영남", "영남", "수도권"),
  stringsAsFactors = FALSE
)

ui <- fluidPage(
  checkboxGroupInput("region_filter", "지역 필터",
                     choices  = c("수도권", "영남"),
                     selected = c("수도권", "영남"),
                     inline   = TRUE),
  leafletOutput("map", height = "400px")
)

server <- function(input, output, session) {
  # 초기 지도 한 번만 렌더링
  output$map <- renderLeaflet({
    leaflet() %>%
      addTiles() %>%
      setView(lng = 127.9, lat = 36.5, zoom = 7)
  })

  # 필터 변경 시 마커만 업데이트
  observe({
    filtered <- cities[cities$region %in% input$region_filter, ]

    leafletProxy("map") %>%
      clearMarkers() %>%   # 기존 마커 삭제
      addMarkers(
        data  = filtered,
        lng   = ~lng,
        lat   = ~lat,
        label = ~name,
        popup = ~paste0("<b>", name, "</b><br>지역: ", region)
      )
  })
}

shinyApp(ui, server)

leafletProxy("map")은 이미 렌더링된 지도에 접근합니다. clearMarkers(), clearShapes(), clearHeatmap() 등으로 기존 레이어를 지우고 새로 추가합니다.

주요 레이어 함수 정리

함수 용도
addTiles() OpenStreetMap 기본 타일
addProviderTiles() 다양한 외부 지도 타일
addMarkers() 핀 마커
addCircleMarkers() 원형 마커
addCircles() 반지름이 있는 원(미터 단위)
addPolygons() 폴리곤(행정 구역 등)
addPolylines() 선(도로, 경로 등)
addHeatmap() 히트맵 (leaflet.extras 패키지)
addLegend() 색상 범례
addMiniMap() 미니 지도 (leaflet.extras 패키지)