Gdal Container + psql + Shell Script 를 활용한 GIS 데이터 일괄 업로드 방법

식빵·2025년 1월 18일
0

GIS

목록 보기
9/9

준비물

Docker 를 설치한 환경에서 다음과 같은 DockerFile 을 작성합니다.

FROM ghcr.io/osgeo/gdal:ubuntu-small-latest

ENV LC_ALL=C.UTF-8

RUN ln -sf /usr/share/zoneinfo/Asia/Seoul /etc/localtime

RUN apt update && apt-get install -y --no-install-recommends postgresql-client vim

이후 아래처럼 build, run 합니다.
-v 옵션은 여러분들 상황에 맞게 바꿔주세요.

docker build -t gdal_build .
docker run --name gdal -v 'C:\gdal_docker\bind_dir:/bind_dir' -itd gdal_build

생성된 container 에 bash 를 실행해서 컨테이너 내부로 접속합니다.

docker exec -it gdal bash

여기까지하면 준비는 끝입니다.



예시 데이터

이 게시물에서 사용하는 예시 데이터를 다운로드 받는 방법은 다음과 같습니다.


https://business.juso.go.kr/addrlink/elctrnMapProvd/geoDBDwldList.do?menu=5
위 링크에 접속해서 구역의 도형(.shp) 목차로 가서
전체자료 의 가장 최신 데이터(2024-12)를 사용했습니다.
(데이터를 받으려면 신청을 해야됩니다)


데이터 신청을 완료하면 위 그림처럼 각 시도별로 구역의 도형 정보를 받을 수 있습니다.


하나의 zip 파일에는 위 그림처럼 여러 shape 정보가 혼재되어 있습니다.
이 중에서 사용할 데이터는 다음과 같습니다.

  • TL_SCCO_CTPRVN.* : 행정경계(법정동)_시도
  • TL_SCCO_SIG.* : 행정경계(법정동)_시군구
  • TL_SCCO_EMD.* : 행정경계(법정동)_읍면동
  • TL_SCCO_LI.* : 행정경계(법정동)_리



목표

위에서 다운로드 받은 zip 데이터를 압축해제 하면 다음과 같습니다.

이렇게 압축해제된 상태에서 하고자 하는 일은 다음과 같습니다.

  1. 데이터를 Import 할 테이블을 4개(시도, 시군구, 읍면동, 리) 생성합니다.
    테이블 이름 충돌을 피하기 위해서 테이블 이름에 임시 명칭을 줍니다.
  1. 위 그림처럼 압축해제가 완료된 상태의 디렉토리에서 재귀적으로 순회하며
    업로드에 필요한 *.shp 을 찾아냅니다.
  1. 찾아낸 shape 파일을 PostgreSQL DB Table 에 업로드합니다.
    이때 찾아낸 shp 파일이 어떤 타입(시도, 시군구, 읍면동, 리)인지에 따라
    다른 테이블에 Import 를 진행합니다.
  1. Import 가 완료되면 임시 테이블 명을 실제로 비즈니스에서 사용하고자 하는
    테이블 명으로 수정합니다. 최종 수정한 테이블명은 데이터의 기준일자가
    끝에 붙습니다. ex) tl_scco_ctprvn_202412



스크립트 작성 예시

#!/bin/bash

# 에러나면 전체 스크립트 정지
set -e

# 총 걸린 시간을 추출하기 위해 "시작 시간"을 미리 추출 
start_time=$SECONDS


# 함수 정의

## 1. shape file upload 함수.
##    ShapeFile 이 모여있는 Directory 내부를 재귀적으로 순회하면서
##    file_pattern 에 매칭되는 파일들을 찾아낸다(주로 shp 파일이다).
##    찾아낸 이후에는 target_table 에 ogr2ogr 로 데이터를 업로드한다.
upload_shapefile() {
    local file_pattern="$1"
    local target_table="$2"
    echo "find file pattern => ${file_pattern} .. and upload to => ${target_table}"

    for SHP_FILE in $(find "${SHAPEFILES_DIR}" -type f -name "${file_pattern}"); do
      if [ -f "$SHP_FILE" ]; then
        echo "Processing $SHP_FILE"
        ogr2ogr -append -progress -f PostgreSQL \
          "PG:host=${DB_HOST} dbname=${DB_NAME} port=${DB_PORT} user=${DB_USER} password=${DB_PASSWORD} schemas=${DB_SCHEMA}" \
          "${SHP_FILE}" -nlt PROMOTE_TO_MULTI -nln "${target_table}" \
          -s_srs EPSG:5179 -t_srs EPSG:5186 --config SHAPE_ENCODING "EUC-KR" \
          --config PG_USE_COPY YES;
      else
        echo "No ${file_pattern} files found in ${SHAPEFILES_DIR}"
        exit 1
      fi
    done
}


## 2. 인덱스 생성 함수.
##    데이터를 모두 테이블에 업로드 한 후에 인덱스를 생성할 때 필요한 함수이다.
create_index() {
    local table_name="$1"

    echo "Creating Index on [geom] column"
    psql -h "$DB_HOST" -p "$DB_PORT" -U "$DB_USER" -d "$DB_NAME" -c "
    create index ${table_name}_geom_idx
        on ${DB_SCHEMA}.${table_name} using gist (geom);
    "
}

## 3. 권한 변경 함수.
##    테이블과 관련된 권한 변경이 필요할 때 사용하는 함수이다.
update_authorization() {
    local table_name="$1"
    # echo "Change Table [${table_name}] Authorization"
    # psql -h "$DB_HOST" -p "$DB_PORT" -U "$DB_USER" -d "$DB_NAME" -c "
    # alter table ${DB_SCHEMA}.${table_name} owner to tester1;
    # grant delete, insert, select, update on ${DB_SCHEMA}.${table_name} to tester2;
    # "
}


## 4. 임시 테이블 명칭을 실제 운영할 때 사용하는 테이블 명으로 바꿔치기 합니다.
rename_temp_to_prod() {

    local temp_table_name="$1"
    local prod_table_name="$2"

    echo "Renaming Tables [${temp_table_name}] ==> [${prod_table_name}_${DATA_STD_DATE}]"

    ## 만약 기존 운영 테이블이 있다면 해당 테이블에 "_old" 를 붙여서
    ## 충돌을 미연에 방지할려면 조금 작업이 필요하겠죠?
    ## 이건 스스로 해보시기 바랍니다!

    ## 5. 임시 테이블 ==> 운영 테이블 명으로 변경
    echo "RENAME TEMP TABLE TO PRODCUTION TABLE"
    psql -h "$DB_HOST" -p "$DB_PORT" -U "$DB_USER" -d "$DB_NAME" -c "
    alter table ${DB_SCHEMA}.${temp_table_name} RENAME TO ${prod_table_name}_${DATA_STD_DATE};
    "
}

# PostgreSQL 연결 정보
DB_HOST="host.docker.internal"
DB_PORT="5432"
DB_NAME="postgres"
DB_USER="postgres"
DB_SCHEMA="public"
DB_PASSWORD="root"
DATA_STD_DATE="202412"


########### 파일들 압축해제 ###########
# for file in /bind_dir/TL_SCCO_AREA/*.zip; do unzip -o "$file" -d "$(dirname "$file")"; done

########### 변수 세팅 ###########

# 임시 테이블 명
CURRENT_DATETIME=$(date +"%Y%m%d_%H%M%S")

PROD_CTPRVN_TABLE="tl_scco_ctprvn"
PROD_SIG_TABLE="tl_scco_sig"
PROD_EMD_TABLE="tl_scco_emd"
PROD_LI_TABLE="tl_scco_li"

TEMP_CTPRVN_TABLE="${PROD_CTPRVN_TABLE}_${CURRENT_DATETIME}"
TEMP_SIG_TABLE="${PROD_SIG_TABLE}_${CURRENT_DATETIME}"
TEMP_EMD_TABLE="${PROD_EMD_TABLE}_${CURRENT_DATETIME}"
TEMP_LI_TABLE="${PROD_LI_TABLE}_${CURRENT_DATETIME}"

SHAPEFILES_DIR="/bind_dir/TL_SCCO_AREA"


########### 0. 비밀번호 설정 ##########
export PGPASSWORD=$DB_PASSWORD

########### 1. 임시 테이블 생성 ##########
# 기존 geonpaas table 스키마 최대한 그대로 유지

# 1-1. 시도 레벨 테이블 생성
echo "Creating temporary 'CTPRVN' table: $TEMP_CTPRVN_TABLE";
psql -h "$DB_HOST" -p "$DB_PORT" -U "$DB_USER" -d "$DB_NAME" -c "
create table ${DB_SCHEMA}.${TEMP_CTPRVN_TABLE}
(
    ogc_fid        serial not null primary key,
    ctprvn_cd  varchar(5),
    ctp_eng_nm varchar(40),
    ctp_kor_nm varchar(40),
    geom       geometry(MultiPolygon, 5186)
);

comment on table ${DB_SCHEMA}.${TEMP_CTPRVN_TABLE} is '행정경계_시도(법정동)';
"


## 1-2. 시군구 레벨 테이블 생성
echo "Creating temporary 'SIG' table: $TEMP_SIG_TABLE";
psql -h "$DB_HOST" -p "$DB_PORT" -U "$DB_USER" -d "$DB_NAME" -c "
create table ${DB_SCHEMA}.${TEMP_SIG_TABLE}
(
    ogc_fid        serial not null primary key,
    sig_cd     varchar(5),
    sig_eng_nm varchar(40),
    sig_kor_nm varchar(40),
    geom       geometry(MultiPolygon, 5186)
);

comment on table ${DB_SCHEMA}.${TEMP_SIG_TABLE} is '행정경계_시군구(법정동)';
"


## 1-3. 읍면동 레벨 테이블 생성
echo "Creating temporary 'EMD' table: $TEMP_EMD_TABLE";
psql -h "$DB_HOST" -p "$DB_PORT" -U "$DB_USER" -d "$DB_NAME" -c "
create table ${DB_SCHEMA}.${TEMP_EMD_TABLE}
(
    ogc_fid        serial not null primary key,
    emd_cd     varchar(10),
    emd_eng_nm varchar(40),
    emd_kor_nm varchar(40),
    geom       geometry(MultiPolygon, 5186)
);

comment on table ${DB_SCHEMA}.${TEMP_EMD_TABLE} is '행정경계_읍면동(법정동)';
"


## 1-4. 리 레벨 테이블 생성
echo "Creating temporary 'LI' table: $TEMP_LI_TABLE";
psql -h "$DB_HOST" -p "$DB_PORT" -U "$DB_USER" -d "$DB_NAME" -c "
create table ${DB_SCHEMA}.${TEMP_LI_TABLE}
(
    ogc_fid        serial not null primary key,
    li_cd     varchar(10),
    li_eng_nm varchar(40),
    li_kor_nm varchar(40),
    geom      geometry(MultiPolygon, 5186)
);

comment on table ${DB_SCHEMA}.${TEMP_LI_TABLE} is '행정경계_리(법정동)';
"

########### 2. shapefile 데이터 업로드 ##########
upload_shapefile "TL_SCCO_CTPRVN.shp" "${TEMP_CTPRVN_TABLE}"
upload_shapefile "TL_SCCO_SIG.shp" "${TEMP_SIG_TABLE}"
upload_shapefile "TL_SCCO_EMD.shp" "${TEMP_EMD_TABLE}"
upload_shapefile "TL_SCCO_LI.shp" "${TEMP_LI_TABLE}"


# ################## 3. 인덱스 생성 및 권한 변경 ##################
create_index ${TEMP_CTPRVN_TABLE}
create_index ${TEMP_SIG_TABLE}
create_index ${TEMP_EMD_TABLE}
create_index ${TEMP_LI_TABLE}


# ################## 4. 권한 관련사항 변경 ##################
update_authorization ${TEMP_CTPRVN_TABLE}
update_authorization ${TEMP_SIG_TABLE}
update_authorization ${TEMP_EMD_TABLE}
update_authorization ${TEMP_LI_TABLE}



# ############### 5. 기존 상용 테이블과 이름 바꿔치기 ###############
rename_temp_to_prod  ${TEMP_CTPRVN_TABLE} ${PROD_CTPRVN_TABLE}
rename_temp_to_prod  ${TEMP_SIG_TABLE} ${PROD_SIG_TABLE}
rename_temp_to_prod  ${TEMP_EMD_TABLE} ${PROD_EMD_TABLE}
rename_temp_to_prod  ${TEMP_LI_TABLE} ${PROD_LI_TABLE}


echo "+==================================================================================================+"
echo "+==================================================================================================+"
end_time=$SECONDS
echo "Completed uploading TL_SCCO_*.shp files in ... $((end_time - start_time)) seconds!"
echo "+==================================================================================================+"
echo "+==================================================================================================+"

# Completed uploading TL_SCCO_*.shp files in ... 33 seconds!
profile
백엔드를 계속 배우고 있는 개발자입니다 😊

0개의 댓글