용도지구 현황

출처: (서울열린데이터광장) 서울특별시, 서울특별시 기본통계, 용도지구 현황

# R 패키지 및 사용자 정의 함수 불러오기
if (!require("here")) install.packages("here")
library(here)
source(here("scripts/_library.R"))

# 환경별 가상환경 경로 설정
venv_path <- if (.Platform$OS.type == "windows") {
  here("si_venv_win") # Windows용 가상환경
} else if (Sys.info()["sysname"] == "Darwin") {
  here("si_venv_mac") # macOS용 가상환경
} else {
  here("si_venv") # Linux용 가상환경
}

# 선택한 가상환경 활성화
reticulate::use_virtualenv(venv_path, required = TRUE)
repl_python()
exit
import pandas as pd
from pyprojroot import here
# 용도지역 현황 데이터 불러오기
purpDstrStat = pd.read_csv(here("data/raw/용도지구+현황.csv"), encoding='utf-8')
purpDstrStat['데이터'] = purpDstrStat['데이터'].str.replace('-','0')
purpDstrStat['데이터'] = purpDstrStat['데이터'].fillna(0)
purpDstrStat['데이터'] = purpDstrStat['데이터'].astype(int)
purpDstrStat['데이터'] = purpDstrStat['데이터']/1000000
bsYr = purpDstrStat['시점'].max()
#1분류 용도지구
purpDstrStat1 = pd.pivot_table(purpDstrStat, index = ['구분별(1)'], columns = ['시점'], values = '데이터', aggfunc='max')
purpDstrStat1 = purpDstrStat1.fillna(0)
# purpDstrStat1 = purpDstrStat1.transpose()
# # purpAStat1.reset_index(inplace=True)
#2분류 용도지구
purpDstrStat2 = pd.pivot_table(purpDstrStat, index = ['시점'], columns = ['구분별(1)','구분별(2)','구분별(3)'], values = '데이터', aggfunc='max')
purpDstrStat2 = purpDstrStat2.fillna(0)
# purpDstrStat1 = purpDstrStat1.transpose()
# # purpAStat1.reset_index(inplace=True)
protect = purpDstrStat2[[
            ('보호지구','소계','소계'),
            ('보호지구','생태계보호지구','소계'),
            ('보호지구','역사문화환경보호지구','소계'),
            ('보호지구','중요시설물보호시설','소계'),
            ('보호지구','중요시설물보호시설','공용시설보호지구'),
            ('보호지구','중요시설물보호시설','공항시설보호지구'),
            ('보호지구','중요시설물보호시설', '중요시설보호지구')]]
scene = purpDstrStat2[[
          ('경관지구','소계','소계'),
          ('경관지구','수변','소계'),
          ('경관지구','시가지','소계'),
          ('경관지구','특화','역사문화'),
          ('경관지구','특화','조망가로'),
          ('경관지구','시계','소계'),
          ('경관지구','자연','소계')]]
totPurpDstr = purpDstrStat1.at['합계',bsYr]
dstrAreaByPurp = purpDstrStat1.drop(index=['합계']).sort_values(by=bsYr, ascending=False)
dstrAreaPurpRnkNm = dstrAreaByPurp.index.tolist()
dstrAreaPurpRnkVl = dstrAreaByPurp[bsYr].tolist()
dstrAreaPurpRnkWght = ((dstrAreaByPurp[bsYr]/totPurpDstr)*100).tolist()

dstrAreaPurpRnk = ", ".join([f"{dstrAreaPurpRnkNm[i]} {dstrAreaPurpRnkVl[i]:.1f}㎢({dstrAreaPurpRnkWght[i]:.1f}%)" for i in range(len(dstrAreaPurpRnkNm)) if dstrAreaPurpRnkVl[i] != 0 and dstrAreaPurpRnkWght[i] != 0])
protectTot = protect.at[bsYr,('보호지구','중요시설물보호시설','소계')]
protectArprt = protect.at[bsYr,('보호지구','중요시설물보호시설','공항시설보호지구')]
protectPblc = protect.at[bsYr,('보호지구','중요시설물보호시설','공용시설보호지구')]
protectImprt = protect.at[bsYr,('보호지구','중요시설물보호시설','중요시설보호지구')]
sceneNtr = scene.at[bsYr,('경관지구','자연','소계')]
sceneSpcl = scene.at[bsYr,('경관지구','특화','역사문화')]+scene.at[bsYr,('경관지구','특화','조망가로')]
sceneDntn = scene.at[bsYr,('경관지구','시가지','소계')]
print(f"서울시 용도지구 면적은 {totPurpDstr:.1f}㎢로 전체 면적의 15.3% 차지")
서울시 용도지구 면적은 92.9㎢로 전체 면적의 15.3% 차지
print(f"‐{bsYr}년 용도지구는 {dstrAreaPurpRnk} 순으로 지정")
‐2023년 용도지구는 보호지구 59.5㎢(64.0%), 경관지구 16.3㎢(17.6%), 고도지구 9.2㎢(9.9%), 방화지구 3.4㎢(3.7%), 개발진흥지구 3.0㎢(3.2%), 문화지구 1.0㎢(1.1%), 취락지구 0.5㎢(0.5%) 순으로 지정
print(f"보호지구는 용도지구 중 가장 많은 면적({(protectTot/totPurpDstr)*100:.1f}%)을 차지하며 공항시설({(protectArprt/totPurpDstr)*100:.1f}%), 공용시설({(protectPblc/totPurpDstr)*100:.1f}%), 중요시설({(protectImprt/totPurpDstr)*100:.1f}%)로 구성. 주로 강서구와 양천구에 집중 분포")
보호지구는 용도지구 중 가장 많은 면적(64.0%)을 차지하며 공항시설(62.3%), 공용시설(1.0%), 중요시설(0.7%)로 구성. 주로 강서구와 양천구에 집중 분포
print(f"경관지구는 자연({(sceneNtr/totPurpDstr)*100:.1f}%), 특화({(sceneSpcl/totPurpDstr)*100:.1f}%) 시가지({(sceneDntn/totPurpDstr)*100:.1f}%)로 구성. 종로구, 성북구, 서대문구 등에 분포")
경관지구는 자연(13.3%), 특화(4.0%) 시가지(0.2%)로 구성. 종로구, 성북구, 서대문구 등에 분포
print(f"고도지구는 주요 산(남산, 북한산)이나 시설물(경복궁, 국회의사당) 주변에 위치")
고도지구는 주요 산(남산, 북한산)이나 시설물(경복궁, 국회의사당) 주변에 위치
print(f"방화지구는 50% 이상이 중구에 위치")
방화지구는 50% 이상이 중구에 위치
print(f"개발진흥지구는 종로 귀금속지구, 성수 IT지구, 마포 디자인출판지구 등 동일 산업이 밀집한 특정 지역에 위치")
개발진흥지구는 종로 귀금속지구, 성수 IT지구, 마포 디자인출판지구 등 동일 산업이 밀집한 특정 지역에 위치
print(f"문화지구는 전통문화, 공연예술문화, 음악문화진흥을 위해 지정되어 있으며 종로구, 서초구에 위치")
문화지구는 전통문화, 공연예술문화, 음악문화진흥을 위해 지정되어 있으며 종로구, 서초구에 위치
print(f"취락지구는 강동구, 서초구, 강남구 등에 분포")
취락지구는 강동구, 서초구, 강남구 등에 분포
# (그림1-2) 용도지구 현황 --chart
areaByPurp = purpDstrStat1.drop(index='합계')
areaByPurp = areaByPurp[[bsYr]]
Z1 = areaByPurp.div(areaByPurp.sum(axis=0), axis=1) * 100
Z1 = Z1.reindex(['경관지구','고도지구','방화지구','보호지구','취락지구','개발진흥지구','문화지구'])
areaByPurpSpc = purpDstrStat2[[
                ('경관지구','자연','소계'),
                ('경관지구','시가지','소계'),
                ('경관지구','특화','역사문화'),
                ('경관지구','특화','조망가로'),
                ('고도지구','소계','소계'),
                ('방화지구','소계','소계'),
                ('보호지구','중요시설물보호시설','공용시설보호지구'),
                ('보호지구','중요시설물보호시설','공항시설보호지구'),
                ('보호지구','중요시설물보호시설','중요시설보호지구'),
                ('취락지구','소계','소계'),
                ('개발진흥지구','소계','소계'),
                ('문화지구','소계','소계')
                ]]

areaByPurpSpc[('경관지구', '특화', '소계')] = areaByPurpSpc[('경관지구', '특화', '역사문화')] + areaByPurpSpc[('경관지구', '특화', '조망가로')]
col = areaByPurpSpc.pop(('경관지구', '특화', '소계'))
areaByPurpSpc.insert(2, ('경관지구', '특화', '소계'), col)
areaByPurpSpc = areaByPurpSpc.drop(columns=[('경관지구', '특화', '역사문화'), ('경관지구', '특화', '조망가로')])
areaByPurpSpc.columns = areaByPurpSpc.columns.get_level_values(2)
areaByPurpSpc.columns = [['자연','시가지','특화','고도지구','방화지구','공용시설','공항시설','중요시설','취락지구','개발진흥지구','문화지구']]
# (그림1-3) 용도지구 현황(세부) --chart
areaByPurpSpc = areaByPurpSpc.transpose()
areaByPurpSpc = areaByPurpSpc[[bsYr]]
Z2 = areaByPurpSpc.div(areaByPurpSpc.sum(axis=0), axis=1) * 100
Z2_reset = Z2.reset_index()
# 가공된 데이터 저장
purpsDist <- py$Z1
purpsDist <- purpsDist |> rownames_to_column("index")
purpsDist <- purpsDist |> as_tibble()

saveRDS(purpsDist, here("data/processed/purps_dist.rds"))  # 분류 비중

purpsDist2 <- py$Z2_reset
names(purpsDist2) <- c("index", "2023")
purpsDist2 <- purpsDist2 |> as_tibble()

saveRDS(purpsDist2, here("data/processed/purps_dist2.rds"))  # 세부 분류 비중

시각화

[그림 2-42] 용도지구 현황

ls_files <- list.files(
  path = here("data/raw/용도지구"),
  pattern = "*.shp$",
  full.names = TRUE,
  recursive = TRUE
)

uqs <- map(ls_files, ~ {
  st_read(.x) |>
    mutate(file_name = .x)
})
Reading layer `UPIS_C_UQ131' from data source 
  `C:\Users\nimgnos\pp_2024-ir-05\data\raw\용도지구\기타용도지구\UPIS_C_UQ131.shp' 
  using driver `ESRI Shapefile'
Simple feature collection with 39 features and 15 fields
Geometry type: MULTIPOLYGON
Dimension:     XY
Bounding box:  xmin: 198431.7 ymin: 440684.7 xmax: 204421.6 ymax: 454007.4
Projected CRS: Korean 1985 / Modified Central Belt
Reading layer `UPIS_C_UQ129' from data source 
  `C:\Users\nimgnos\pp_2024-ir-05\data\raw\용도지구\용도지구(개발진흥지구)\UPIS_C_UQ129.shp' 
  using driver `ESRI Shapefile'
Simple feature collection with 11 features and 15 fields
Geometry type: POLYGON
Dimension:     XY
Bounding box:  xmin: 192299.1 ymin: 440252.1 xmax: 207559.3 ymax: 454667.7
Projected CRS: Korean 1985 / Modified Central Belt
Reading layer `UPIS_C_UQ121' from data source 
  `C:\Users\nimgnos\pp_2024-ir-05\data\raw\용도지구\용도지구(경관지구)\UPIS_C_UQ121.shp' 
  using driver `ESRI Shapefile'
Simple feature collection with 40 features and 15 fields
Geometry type: MULTIPOLYGON
Dimension:     XY
Bounding box:  xmin: 186614.2 ymin: 441436.8 xmax: 211805.1 ymax: 462610.7
Projected CRS: Korean 1985 / Modified Central Belt
Reading layer `UPIS_C_UQ123' from data source 
  `C:\Users\nimgnos\pp_2024-ir-05\data\raw\용도지구\용도지구(고도지구)\UPIS_C_UQ123.shp' 
  using driver `ESRI Shapefile'
Simple feature collection with 42 features and 15 fields
Geometry type: MULTIPOLYGON
Dimension:     XY
Bounding box:  xmin: 192323.2 ymin: 446560.6 xmax: 205917.9 ymax: 464985.4
Projected CRS: Korean 1985 / Modified Central Belt
Reading layer `UPIS_C_UQ124' from data source 
  `C:\Users\nimgnos\pp_2024-ir-05\data\raw\용도지구\용도지구(방화지구)\UPIS_C_UQ124.shp' 
  using driver `ESRI Shapefile'
Simple feature collection with 24 features and 15 fields
Geometry type: MULTIPOLYGON
Dimension:     XY
Bounding box:  xmin: 191564.2 ymin: 446398.5 xmax: 204016.5 ymax: 453866.9
Projected CRS: Korean 1985 / Modified Central Belt
Reading layer `UPIS_C_UQ126' from data source 
  `C:\Users\nimgnos\pp_2024-ir-05\data\raw\용도지구\용도지구(보호지구)\UPIS_C_UQ126.shp' 
  using driver `ESRI Shapefile'
Simple feature collection with 7 features and 15 fields
Geometry type: MULTIPOLYGON
Dimension:     XY
Bounding box:  xmin: 179119.9 ymin: 446482.8 xmax: 193801.6 ymax: 455874.9
Projected CRS: Korean 1985 / Modified Central Belt
Reading layer `UPIS_C_UQ128' from data source 
  `C:\Users\nimgnos\pp_2024-ir-05\data\raw\용도지구\용도지구(취락지구)\UPIS_C_UQ128.shp' 
  using driver `ESRI Shapefile'
Simple feature collection with 38 features and 15 fields
Geometry type: MULTIPOLYGON
Dimension:     XY
Bounding box:  xmin: 185945.6 ymin: 436892.7 xmax: 214903.6 ymax: 461531.3
Projected CRS: Korean 1985 / Modified Central Belt
Reading layer `UPIS_C_UQ130' from data source 
  `C:\Users\nimgnos\pp_2024-ir-05\data\raw\용도지구\용도지구(특정용도제한지구)\UPIS_C_UQ130.shp' 
  using driver `ESRI Shapefile'
Simple feature collection with 0 features and 15 fields
Bounding box:  xmin: NA ymin: NA xmax: NA ymax: NA
Projected CRS: Korean 1985 / Modified Central Belt
uqs <- bind_rows(uqs)

uqs$uqsNm <- str_extract(uqs$file_name, "\\((.*?)\\)") %>%
  str_remove_all("[()]")

uqs$uqsNm <- factor(uqs$uqsNm, levels = c("보호지구", "경관지구", "고도지구", "방화지구", "개발진흥지구", "취락지구"))

uqs <- uqs |>
  filter(!is.na(uqsNm)) |>
  st_transform(4326)

mapPal <- colorFactor(
  palette = c(
    "#262966", "#EECA45", "#CD4E7B",
    "#269442", "#C0C0BB", "#453073", "#714877"
  ),
  domain = uqs$uqsNm
)

seoulSig <- st_read(here("data/processed/seoul_district.geojson"))
Reading layer `seoul_district' from data source 
  `C:\Users\nimgnos\pp_2024-ir-05\data\processed\seoul_district.geojson' 
  using driver `GeoJSON'
Simple feature collection with 25 features and 3 fields
Geometry type: POLYGON
Dimension:     XY
Bounding box:  xmin: 126.7645 ymin: 37.4283 xmax: 127.1838 ymax: 37.70146
Geodetic CRS:  WGS 84
fig242_1 <- leaflet(
  options = leafletOptions(
    dragging = FALSE,
    zoomControl = FALSE,
    doubleClickZoom = FALSE,
    scrollWheelZoom = FALSE
  )
) |>
  addProviderTiles(providers$CartoDB.Positron) |>
  addPolygons(
    data = uqs,
    fillColor = ~ mapPal(uqsNm),
    fillOpacity = 1,
    color = "transparent"
  ) |>
  addPolygons(
    data = seoulSig,
    color = "#222222",
    fillColor = "transparent",
    weight = 1,
    opacity = 1
  ) |>
  addLabelOnlyMarkers(
    data = seoulSig |>
      mutate(centroids = st_centroid(geometry) |> st_coordinates()),
    lng = ~ centroids[, 1],
    lat = ~ centroids[, 2],
    label = ~sig_kor_nm,
    labelOptions = labelOptions(
      noHide = TRUE,
      direction = "center",
      textOnly = TRUE,
      style = list(
        "font-size" = "10px",
        "font-weight" = "bold",
        "color" = "#222222"
      )
    )
  ) |>
  addLegend(
    "topleft",
    pal = mapPal,
    values = uqs$uqsNm,
    title = "용도지구 현황",
    opacity = 0.8
  )

fig242_1
purpsDist <- readRDS(here("data/processed/purps_dist.rds"))

purpsDist <- purpsDist |>
  pivot_longer(
    cols = -index,
    names_to = "year",
    values_to = "value"
  )

purpsDist <- purpsDist |>
  mutate(
    index = factor(index, levels = rev(unique(purpsDist$index)))
  )

fig242_1 <- highchart() |>
  hc_chart(type = "bar", height = "250px") |>
  hc_title(text = "<b>[그림 2-42] 용도지구 현황</b>") |>
  hc_subtitle(text = "자료: 서울시, 용도지구 현황, 2023") |>
  hc_xAxis(
    title = list(text = ""),
    labels = list(enabled = FALSE)
  ) |>
  hc_yAxis(
    title = list(text = ""),
    labels = list(format = "{value}%"),
    max = 60,
    scrollbar = list(
      enabled = TRUE, # 스크롤바 활성화
      barBackgroundColor = "#e3e3e3", # 스크롤바 배경색 (밝은 회색)
      barBorderRadius = 8, # 스크롤바 모서리 반경
      barBorderWidth = 0, # 스크롤바 테두리 없음
      buttonBackgroundColor = "#f9f9f9", # 버튼 배경색 (중간 회색)
      buttonBorderRadius = 5, # 버튼 모서리 반경
      buttonBorderWidth = 0, # 버튼 테두리 없음
      trackBackgroundColor = "#f9f9f9", # 트랙 배경색 (밝은 회색)
      trackBorderRadius = 5, # 트랙 모서리 반경
      trackBorderWidth = 0, # 트랙 테두리 없음
      height = 10 # 스크롤바 높이
    )
  ) |>
  hc_plotOptions(
    series = list(
      stacking = "percent",
      dataLabels = list(
        enabled = TRUE,
        format = "{point.name}: {point.y:.1f}%",
        style = list(fontSize = "12px")
      )
    )
  ) |>
  hc_add_series(
    data = purpsDist,
    type = "bar",
    hcaes(x = index, y = value, group = index)
  ) |>
  hc_colors(rev(c(
    "#EECA45", "#CD4E7B", "#269442",
    "#262966", "#714877", "#C0C0BB", "#452F72"
  ))) |>
  hc_tooltip(
    shared = FALSE, # 툴팁을 개별 막대에 대해 표시
    headerFormat = "<b>{point.key}</b><br/>", # 툴팁 제목: X축 값
    pointFormat = "{point.y:.1f}%" # 툴팁 포맷: 소수점 1자리와 '만 명' 표시
  ) |>
  hc_legend(
    enabled = TRUE,
    reversed = TRUE,
    align = "center",
    layout = "horizontal",
    verticalAlign = "bottom",
    squareSymbol = FALSE,
    symbolHeight = 10,
    symbolWidth = 20,
    symbolRadius = 0,
    itemWidth = 100
  )

purpsDist2 <- readRDS(here("data/processed/purps_dist2.rds"))

purpsDist2 <- purpsDist2 |>
  pivot_longer(
    cols = -index,
    names_to = "year",
    values_to = "value"
  )

purpsDist2 <- purpsDist2 |>
  mutate(
    index = factor(index, levels = rev(unique(purpsDist2$index)))
  )

fig242_2 <- highchart() |>
  hc_chart(type = "bar", height = "250px") |>
  hc_title(text = "<b>[그림 2-42] 용도지구 세부 현황</b>") |>
  hc_subtitle(text = "자료: 서울시, 용도지구 현황, 2023") |>
  hc_xAxis(
    title = list(text = ""),
    labels = list(enabled = FALSE)
  ) |>
  hc_yAxis(
    title = list(text = ""),
    labels = list(format = "{value}%"),
    max = 60,
    scrollbar = list(
      enabled = TRUE, # 스크롤바 활성화
      barBackgroundColor = "#e3e3e3", # 스크롤바 배경색 (밝은 회색)
      barBorderRadius = 8, # 스크롤바 모서리 반경
      barBorderWidth = 0, # 스크롤바 테두리 없음
      buttonBackgroundColor = "#f9f9f9", # 버튼 배경색 (중간 회색)
      buttonBorderRadius = 5, # 버튼 모서리 반경
      buttonBorderWidth = 0, # 버튼 테두리 없음
      trackBackgroundColor = "#f9f9f9", # 트랙 배경색 (밝은 회색)
      trackBorderRadius = 5, # 트랙 모서리 반경
      trackBorderWidth = 0, # 트랙 테두리 없음
      height = 10 # 스크롤바 높이
    )
  ) |>
  hc_plotOptions(
    series = list(
      stacking = "percent",
      dataLabels = list(
        enabled = TRUE,
        format = "{point.name}: {point.y:.1f}%",
        style = list(fontSize = "12px")
      )
    )
  ) |>
  hc_add_series(
    data = purpsDist2,
    type = "bar",
    hcaes(x = index, y = value, group = index)
  ) |>
  hc_colors(rev(c(
    "#F4E293", "#f8EEBD", "#ECD65D",
    "#CD4E7B", "#269442", "#375A8E",
    "#B2CCDB", "#DDE8EF", "#714877",
    "#C0C0BB", "#452F72"
  ))) |>
  hc_tooltip(
    shared = FALSE, # 툴팁을 개별 막대에 대해 표시
    headerFormat = "<b>{point.key}</b><br/>", # 툴팁 제목: X축 값
    pointFormat = "{point.y:.1f}%" # 툴팁 포맷: 소수점 1자리와 '만 명' 표시
  ) |>
  hc_legend(
    enabled = TRUE,
    reversed = TRUE,
    align = "center",
    layout = "horizontal",
    verticalAlign = "bottom",
    squareSymbol = FALSE,
    symbolHeight = 10,
    symbolWidth = 20,
    symbolRadius = 0,
    itemWidth = 100
  )

fig242_1
fig242_2
# 객체 저장
htmlwidgets::saveWidget(fig242_1, here("outputs/figures/fig242_1.html"))
htmlwidgets::saveWidget(fig242_2, here("outputs/figures/fig242_2.html"))