학교와 CCTV

공공데이터를 활용하여 학교와 주변 CCTV를 확인한다.

저자
소속

1 데이터

1.1 행정동 코드

행정안전부 주민등록, 인감 에서 행정기관(행정동) 및 관할구역(법정동) 코드를 다운로드 받을 수 있다.

코드
library(readxl)
library(tidyverse)

admin_code_raw <- read_excel("data/jsocde20230611/KIKcd_H.20230611.xlsx")

admin_code <- admin_code_raw %>% 
  mutate(시도 = str_sub(행정동코드, 1, 2),
         시군구 = str_sub(행정동코드, 1, 4),
         읍면동 = str_sub(행정동코드, 1, 8)) %>% 
  select(행정동코드, 시도, 시도명, 시군구, 시군구명, 읍면동, 읍면동명) 

admin_code %>%
  write_rds("data/admin_code.rds")

admin_code
#> # A tibble: 3,882 × 7
#>    행정동코드 시도  시도명     시군구 시군구명 읍면동   읍면동명  
#>    <chr>      <chr> <chr>      <chr>  <chr>    <chr>    <chr>     
#>  1 1100000000 11    서울특별시 1100   <NA>     11000000 <NA>      
#>  2 1111000000 11    서울특별시 1111   종로구   11110000 <NA>      
#>  3 1111051500 11    서울특별시 1111   종로구   11110515 청운효자동
#>  4 1111053000 11    서울특별시 1111   종로구   11110530 사직동    
#>  5 1111054000 11    서울특별시 1111   종로구   11110540 삼청동    
#>  6 1111055000 11    서울특별시 1111   종로구   11110550 부암동    
#>  7 1111056000 11    서울특별시 1111   종로구   11110560 평창동    
#>  8 1111057000 11    서울특별시 1111   종로구   11110570 무악동    
#>  9 1111058000 11    서울특별시 1111   종로구   11110580 교남동    
#> 10 1111060000 11    서울특별시 1111   종로구   11110600 가회동    
#> # ℹ 3,872 more rows

1.2 초등학교

경기데이터드림 공공데이터 개방포털에서 초․중등학교 위치 현황(제공표준)에서 광명시만 해당 학교를 추출한다.

코드
gg_school <- read_excel("data/초․중등학교위치현황(제공표준).xlsx")

## 광명시 초등학교
km_school <- gg_school %>% 
  filter(학교급구분 == "초등학교",
         교육지원청명 == "경기도광명교육지원청") %>% 
  select(주소 = 소재지도로명주소, 학교명, 경도, 위도, 설립형태, 설립일자) %>% 
  mutate(경도 = as.numeric(경도),
         위도 = as.numeric(위도))

km_school
#> # A tibble: 25 × 6
#>    주소                        학교명          경도  위도 설립형태 설립일자  
#>    <chr>                       <chr>          <dbl> <dbl> <chr>    <chr>     
#>  1 경기도 광명시 가마산로 28   도덕초등학교    127.  37.5 공립     1986-04-25
#>  2 경기도 광명시 성채안로 16   충현초등학교    127.  37.4 공립     2011-03-01
#>  3 경기도 광명시 한내일로 5    구름산초등학교  127.  37.5 공립     2010-03-01
#>  4 경기도 광명시 소하일로 45   소하초등학교    127.  37.4 공립     1997-05-01
#>  5 경기도 광명시 도덕공원로 5  안현초등학교    127.  37.5 공립     2010-03-01
#>  6 경기도 광명시 안현로 16     하안초등학교    127.  37.5 공립     1989-10-12
#>  7 경기도 광명시 금당로 11-7   가림초등학교    127.  37.5 공립     1990-03-26
#>  8 경기도 광명시 가학로 247-55 안서초등학교    127.  37.4 공립     1964-03-04
#>  9 경기도 광명시 범안로 657    온신초등학교    127.  37.4 공립     1947-04-30
#> 10 경기도 광명시 광이로 21     광명초등학교    127.  37.5 공립     1971-03-01
#> # ℹ 15 more rows

1.3 CCTV 정보

2022-01-27 등록된 경기도 광명시_어린이보호구역 내 CCTV 정보_API를 공공데이터포털에서 다운로드 받는다.

코드
library(sf)

cctv_raw <- read_csv("data/보호구역내cctv정보.csv", locale=locale('ko',encoding='euc-kr'))

cctv_tbl <- cctv_raw %>% 
  select(기기번호 = SFZN_ID, 용도 = INST_PURPS, 도로명주소=RDNMADR, 경도, 위도, 설치대수=CCTV_CNT, 일자=REF_DATE)

cctv_tbl
#> # A tibble: 98 × 7
#>    기기번호 용도       도로명주소                 경도  위도 설치대수 일자      
#>       <dbl> <chr>      <chr>                     <dbl> <dbl>    <dbl> <date>    
#>  1       20 어린이보호 경기도 광명시 안현로 80-2  127.  37.5        1 2021-10-01
#>  2       21 다목적     경기도 광명시 소하일로 45  127.  37.4        1 2021-10-26
#>  3       11 다목적     경기도 광명시 금당로 11-7  127.  37.5        4 2021-10-14
#>  4       38 어린이보호 경기도 광명시 한내로13번…  127.  37.5        1 2021-10-12
#>  5       11 다목적     경기도 광명시 금당로 11-7  127.  37.5        1 2021-10-14
#>  6       11 어린이보호 경기도 광명시 금당로 11-7  127.  37.5        1 2021-10-14
#>  7       21 다목적     경기도 광명시 소하일로 45  127.  37.4        1 2021-10-26
#>  8       12 다목적     경기도 광명시 금당로 71    127.  37.5        1 2021-10-14
#>  9       12 어린이보호 경기도 광명시 금당로 71    127.  37.5        1 2021-10-14
#> 10       22 다목적     경기도 광명시 한내일로 5   127.  37.5        1 2021-10-26
#> # ℹ 88 more rows

1.4 지도

행정동 관련 다양한 지도가 존재한다. 국가공간정보포털을 통해 공간정보 포탈의 역할을 수행하였으나, 브이월드로 통합되고 있다. 브이월드는 공간정보오픈플랫폼으로 기존 국가공간정보포탈을 확장한 것으로 보인다.

코드
library(sf)

admin_sf <- st_read("data/HangJeongDong_ver20230401.geojson")
#> Reading layer `HangJeongDong_ver20230401' from data source 
#>   `E:\quarto\map_challenge\data\HangJeongDong_ver20230401.geojson' 
#>   using driver `GeoJSON'
#> Simple feature collection with 3520 features and 10 fields
#> Geometry type: MULTIPOLYGON
#> Dimension:     XY
#> Bounding box:  xmin: 124.6097 ymin: 33.11187 xmax: 131.8713 ymax: 38.61695
#> Geodetic CRS:  WGS 84

km_sf <- admin_sf %>% 
  filter(sgg == "41210") %>% 
  st_transform(crs = 4326) %>% 
  separate(temp, into = c("구시군", "읍면동"), sep = " ")

plot(st_geometry(km_sf))

2 시각화

2.1 CCTV x 행정동

코드
# devtools::install_github("yutannihilation/ggsflabel")
library(ggsflabel)

cctv_sf <- st_as_sf(cctv_tbl, coords = c("경도", "위도"),
           crs = 4326)

cctv_admin_gg <- ggplot() +
  geom_sf(data = cctv_sf, aes(geometry = geometry)) +
  geom_sf(data = km_sf, aes(geometry = geometry), alpha=0) +
  theme_void() +
  geom_sf_label_repel(data = km_sf, aes(label=읍면동))

cctv_admin_gg

2.2 CCTV x 행정동 (인터랙티브)

코드
library(plotly)
library(ggiraph)

info <- glue::glue("&middot; 용도: {cctv_sf$용도}<br>",
                   "&middot; 일자: {cctv_sf$일자}<br>",
                   "&middot; 설치대수: {cctv_sf$설치대수}<br>",
                   "&middot; 위치: {cctv_sf$도로명주소}")

cctv_admin_plotly <- ggplot() +
  geom_sf(data = km_sf, aes(geometry = geometry), alpha=0) +
  geom_sf_interactive(data=cctv_sf, aes(geometry = geometry, tooltip = info, size=as.integer(설치대수),
                                        color = 용도), 
                      size =1) +
  geom_sf_text_repel(data = km_sf, aes(label = 읍면동), color="blue", size = 2) +
  theme_void() +
  labs(color = "CCTV 용도")

girafe(ggobj = cctv_admin_plotly)

3 초등학교 CCTV

3.1 광명시 현황

코드
info <- glue::glue("&middot; 용도: {cctv_sf$용도}<br>",
                   "&middot; 일자: {cctv_sf$일자}<br>",
                   "&middot; 설치대수: {cctv_sf$설치대수}<br>",
                   "&middot; 위치: {cctv_sf$도로명주소}")

km_school_sf  <- 
  st_as_sf(km_school, coords = c("경도", "위도"),
           crs = 4326)

cctv_school_plotly <- ggplot() +
  geom_sf(data = km_sf, aes(geometry = geometry), alpha=0) +
  geom_sf_text_repel(data = km_school_sf, aes(label = 학교명), color="red", size = 2) +
  geom_sf(data = km_school_sf, aes(geometry = geometry), alpha=1) +
  geom_sf_interactive(data=cctv_sf, aes(geometry = geometry, tooltip = info, size=as.integer(설치대수),
                                        color = 용도), size =1) +
  geom_sf_text_repel(data = km_sf, aes(label = 읍면동), color="blue", size = 2) +
  theme_void() +
  labs(color = "CCTV 용도")

girafe(ggobj = cctv_school_plotly)

3.2 동별

3.2.1 스크립트

코드
## 동별지도 
km_dong_sf <- km_sf %>% filter(읍면동 == "소하1동")
school_dong_sf <- st_filter(km_school_sf, km_dong_sf)
cctv_dong_sf <- st_filter(cctv_sf, km_dong_sf)

## 동별 툴팁
dong_info <- glue::glue("&middot; 용도: {cctv_dong_sf$용도}<br>",
                   "&middot; 일자: {cctv_dong_sf$일자}<br>",
                   "&middot; 설치대수: {cctv_dong_sf$설치대수}<br>",
                   "&middot; 위치: {cctv_dong_sf$도로명주소}")


ggplot() +
  ## 행정동 -------------
  geom_sf(data = km_dong_sf , aes(geometry = geometry), alpha=0) +
  geom_sf_text_repel(data = km_dong_sf, aes(label = 읍면동), color="blue", size = 2) +
  ## 학교 ---------------
  geom_sf_text_repel(data = school_dong_sf, aes(label = 학교명), color="red", size = 3) +
  geom_sf(data = school_dong_sf, aes(geometry = geometry), alpha=1) +
  ## CCTV ---------------
  geom_sf_interactive(data=cctv_dong_sf, aes(geometry = geometry, tooltip = dong_info,
                                             size=as.integer(설치대수), color = 용도), size =1) +
  theme_void() +
  labs(color = "CCTV 용도")

3.2.2 함수

코드
draw_cctv_shcool_map <- function(dong_name = "소하1동") {
  ## 동별지도 
  km_dong_sf <- km_sf %>% filter(읍면동 == dong_name)
  school_dong_sf <- st_filter(km_school_sf, km_dong_sf)
  cctv_dong_sf <- st_filter(cctv_sf, km_dong_sf)
  
  ## 동별 툴팁
  dong_info <- glue::glue("&middot; 용도: {cctv_dong_sf$용도}<br>",
                     "&middot; 일자: {cctv_dong_sf$일자}<br>",
                     "&middot; 설치대수: {cctv_dong_sf$설치대수}<br>",
                     "&middot; 위치: {cctv_dong_sf$도로명주소}")
  
  dong_interactive_gg <- ggplot() +
    ## 행정동 -------------
    geom_sf(data = km_dong_sf , aes(geometry = geometry), alpha=0) +
    geom_sf_text_repel(data = km_dong_sf, aes(label = 읍면동), color="blue", size = 5) +
    ## 학교 ---------------
    geom_sf_text_repel(data = school_dong_sf, aes(label = 학교명), color="red", size = 4) +
    geom_sf(data = school_dong_sf, aes(geometry = geometry), alpha=1) +
    ## CCTV ---------------
    geom_sf_interactive(data=cctv_dong_sf, aes(geometry = geometry, tooltip = dong_info,
                                               size=as.integer(설치대수), color = 용도), size =1) +
    theme_void() +
    labs(color = "CCTV 용도",
         title = glue::glue("광명시: {dong_name}"))
  
  # girafe(ggobj = dong_interactive_gg)
  dong_interactive_gg
}

draw_cctv_shcool_map("소하1동")

3.2.3 전체동

코드

dong_plots <- map(km_sf$읍면동, draw_cctv_shcool_map)
names(dong_plots) <- km_sf$읍면동

4 학교, 도로, CCTV

4.1 기본

코드
library(leaflet)
library(fontawesome)

km_dong_sf <- km_sf %>% filter(읍면동 == "소하1동")
school_dong_sf <- st_filter(km_school_sf, km_dong_sf)
cctv_dong_sf <- st_filter(cctv_sf, km_dong_sf)


cctv_icons <- awesomeIconList(
  "다목적" = makeAwesomeIcon(
    icon = "camera",
    markerColor = "blue",
    library = "fa",
    text = fa("camera")
  ),
  "생활방범" = makeAwesomeIcon(
    icon = "camera",
    markerColor = "green",
    library = "fa",
    text = fa("camera")
  ),
  "쓰레기단속" = makeAwesomeIcon(
    icon = "camera",
    markerColor = "purple",
    library = "fa",
    text = fa("camera")
  ),
  "어린이보호" = makeAwesomeIcon(
    icon = "camera",
    markerColor = "red",
    library = "fa",
    text = fa("camera")
  )
)

leaflet(data = cctv_tbl) %>% 
  addProviderTiles(providers$OpenStreetMap) %>% 
  ## 행정동
  addPolygons(data = km_sf, 
              opacity = 1.0, fillOpacity = 0.0,
            weight = 1,
            highlightOptions = highlightOptions(color = "black", weight = 3,  bringToFront = TRUE),
            label = ~읍면동,
            labelOptions = labelOptions(
            style = list("font-weight" = "normal", padding = "3px 8px"),
            textsize = "15px",
            direction = "auto")) %>% 
  addMarkers(data = km_school, lng=~경도, lat=~위도, clusterOptions = markerClusterOptions(),
            popup = ~ as.character(paste0("<strong>", `학교명`, "</strong><br>",
                                          "--------------------------------<br>",
                                          "&middot; 주소: ", `주소`, "<br>",
                                          "&middot; 설립형태: ", `설립형태`, "<br>",
                                          "&middot; 설립일자: ", `설립일자`))) %>% 

  addAwesomeMarkers(data = cctv_tbl, lng=~경도, lat=~위도, clusterOptions = markerClusterOptions(),
                    group = ~용도,
             icon = ~ cctv_icons[용도],
             popup = ~ as.character(paste0("<strong>", 기기번호, "</strong><br>",
                                           "--------------------------------<br>",
                                           "&middot; 설치위치: ", `도로명주소`, "<br>",
                                           "&middot; 설립대수: ", 설치대수, "<br>",
                                           "&middot; 용도: ", 용도)))  

4.2 용도별 인터랙티브

코드
cctv_tbl_split <- split(cctv_tbl, cctv_tbl$용도)

l <- leaflet() %>% addProviderTiles(providers$OpenStreetMap)

unique(cctv_tbl$용도) %>%
  purrr::walk( function(df) {
    l <<- l %>%
    ## 행정동
    addPolygons(data = km_sf, 
                opacity = 1.0, fillOpacity = 0.0,
              weight = 1,
              highlightOptions = highlightOptions(color = "black", weight = 3,  bringToFront = TRUE),
              label = ~읍면동,
              labelOptions = labelOptions(
              style = list("font-weight" = "normal", padding = "3px 8px"),
              textsize = "15px",
              direction = "auto")) %>% 
      
    addMarkers(data = km_school, lng=~경도, lat=~위도, clusterOptions = markerClusterOptions(),
              popup = ~ as.character(paste0("<strong>", `학교명`, "</strong><br>",
                                            "--------------------------------<br>",
                                            "&middot; 주소: ", `주소`, "<br>",
                                            "&middot; 설립형태: ", `설립형태`, "<br>",
                                            "&middot; 설립일자: ", `설립일자`))) %>% 
  
    addAwesomeMarkers(data = cctv_tbl_split[[df]], 
                      lng=~경도, lat=~위도, clusterOptions = markerClusterOptions(),
                      group = df,
               icon = ~ cctv_icons[용도],
               popup = ~ as.character(paste0("<strong>", 기기번호, "</strong><br>",
                                             "--------------------------------<br>",
                                             "&middot; 설치위치: ", `도로명주소`, "<br>",
                                             "&middot; 설립대수: ", 설치대수, "<br>",
                                             "&middot; 용도: ", 용도)))
  })

  l %>%
    addLayersControl(
      overlayGroups = unique(cctv_tbl$용도),
      options = layersControlOptions(collapsed = FALSE)
    )

4.3 PPT

코드
cctv_tbl %>% 
  write_rds("data/km_cctv_tbl.rds")

km_sf %>% 
  write_rds("data/km_km_sf.rds")

km_school %>% 
  write_rds("data/km_km_school.rds")

5 교통사고 통계

5.1 어린이 교통사고

통계청 어린이 교통사고건수(시도/시/군/구)에서 광명시만 특정하여 데이터를 추출한다.

코드
library(readxl)

accident_raw <- read_excel("data/어린이_교통사고건수_시도_시_군_구__20230624195028.xlsx", sheet = "데이터",
           skip = 2)

accident <- accident_raw %>% 
  select(1, 5:7) %>% 
  set_names(c("시점", "사고건수", "사망자수", "부상자수")) %>% 
  mutate_all(as.integer) %>% 
  pivot_longer(cols=-시점, names_to = "구분", values_to = "건수") %>% 
  mutate(건수 = ifelse(is.na(건수), 0, 건수))

accident
#> # A tibble: 51 × 3
#>     시점 구분      건수
#>    <int> <chr>    <dbl>
#>  1  2005 사고건수    93
#>  2  2005 사망자수     1
#>  3  2005 부상자수   106
#>  4  2006 사고건수    77
#>  5  2006 사망자수     0
#>  6  2006 부상자수    90
#>  7  2007 사고건수    60
#>  8  2007 사망자수     0
#>  9  2007 부상자수    62
#> 10  2008 사고건수    48
#> # ℹ 41 more rows
코드
extrafont::loadfonts()

accident %>% 
  ggplot(aes(x = 시점, y=건수, color = 구분)) +
    geom_line() +
    geom_point() +
    labs(title = "광명시 어린이 교통사고건수",
         x = "",
         y = "교통사고건수",
         caption = "출처: KOSIS https://kosis.kr/statHtml/statHtml.do?orgId=101&tblId=DT_1YL202107") +
    theme_minimal(base_family = "MaruBuri")

5.2 어린이 교통사고

도로교통공단 교통사고 GIS 분석시스템 에서 2014~2022년 사이 경기도 광명시 “보행 어린이 사고”를 특정하여 분석한다.

코드
library(janitor)
library(gt)
library(gtExtras)

accident_2016 <- read_excel("data/km_accident_2014_2016.xlsx")
accident_2019 <- read_excel("data/km_accident_2017_2019.xlsx")
accident_2022 <- read_excel("data/km_accident_2020_2022.xlsx")

accident_tbl <- bind_rows(accident_2016, accident_2019) %>% 
  bind_rows(accident_2022)

accident_tbl %>% 
  group_by(사고유형) %>% 
  summarize(사망자수 = sum(사망자수),
            중상자수 = sum(중상자수),
            경상자수 = sum(경상자수)) %>% 
  adorn_totals(where = "row", name = "합계") %>% 
  adorn_totals(where = "col", name = "합계") %>% 
  ## 표 
  gt::gt() %>% 
    tab_header(
      title = md("광명시 **보행 어린이** 교통사고"),
      subtitle = md("`TAAS` 교통사고분석시스템 (2014~2022)")
    ) %>% 
    gt::cols_align(align = "center") %>% 
    tab_spanner(
      label = "교통사고 어린이 구분",
      columns = c(
        사망자수, 중상자수, 경상자수
      )
    ) %>% 
    gt_theme_538() %>% 
     gt_highlight_rows(
       rows = 사고유형 == "차대사람 - 횡단중",
       fill = "lightgrey",
       bold_target_only = TRUE,
       target_col = everything()
     )
광명시 보행 어린이 교통사고
TAAS 교통사고분석시스템 (2014~2022)
사고유형 교통사고 어린이 구분 합계
사망자수 중상자수 경상자수
차대사람 - 기타 0 11 34 45
차대사람 - 길가장자리구역통행중 0 3 12 15
차대사람 - 보도통행중 0 2 5 7
차대사람 - 차도통행중 0 3 8 11
차대사람 - 횡단중 3 33 59 95
차대차 - 기타 0 1 2 3
차대차 - 측면충돌 0 3 0 3
합계 3 56 120 179