작성계기
이 글은 제가 이전에 작성했던 연속지적도를 Partition table 로 만드는 방법 게시물의
내용을 좀 더 쉽게 할 수 있는 방법에 대해 고민을 해봤고,
그 끝에 ps1 을 통해서 더 쉽게 하는 방안을 알아내서 이 글을 작성하게 됐습니다.
주의!
제목에도 나와있지만 반드시 본인 PC 에pwsh
과gdal library
가
설치되어 있어야 합니다! 이미 설치되어 있다면 바로 시작하셔도 됩니다.
설치가 안되어 있다면 아래 링크를 참조해주세요.
- pwsh 설치
- gdal library 설치 (QGIS 우회사용법)
- 이 글에서 설치법 목차를 참조해주세요.
다만 이 방법은 QGIS 설치를 동반해야 되서 약간의 불편함이 있을 수 있습니다.
만약 순수 GDAL 설치를 원하면 따로 찾아보시기 바랍니다.
아래 사항을 어느정도 알고 있다면 읽는데 큰 지장이 없을 겁니다!
GIS
,PostGIS
Partition Table
- Partition Table 이 생소하면? 이 글을 가볍게 보고 오시길 바랍니다.
GIS 분야에서 어느정도 일해봤다면 항상 마주치는 데이터가 있습니다.
바로 연속지적도죠. 얼마나 인기가 좋은지 국가공간정보포털에서 항상 인기검색어
1, 2등을 차지하고 있죠.
그런데 이 데이터가 좀 무겁습니다.
얼마나 무거운지 각 시도별로 데이터를 나눴을 정도니까요.
그렇다면 이렇게 무거우면서도 심지어 여러 개의 파일로 나뉘어진
연속지적도 데이터(=Esri ShapeFile
)를 PostgreSQL DB
에 넣기
위해서는 어떻게 해야될까요?
저는 크게 3가지 작업으로 나눕니다.
DB
에 파티셔닝 테이블 생성Master
) 테이블 생성Child
) 테이블들을 생성ShapeFile
데이터를 insert / append
이 글에서는 위의 과정들을 단 하나의 pwsh 스크립트 (ps1) 으로
할 수 있는 방법을 작성해봤습니다.
실습을 원하시면 연속지적도 데이터를 미리 다운 받으시길 바랍니다. (다운로드 링크)
ps1 스크립트는 pwsh 에서 사용하는 스크립트 파일입니다.
bash 쉘에서 사용하는 shell script
과 비슷한 개념입니다.
그리고 이 ps1 스크립트에 powershell 명령어
+ gdal 명령어
+ psql 명령어
를 적절하게 섞어서 한방에 연속지적도 데이터를 Postgresql 의 Partiton table 로
만들 생각입니다.
지금부터 그 방법을 알아보겠습니다.
먼저 다운로드 받은 모든 연속지적도 zip 파일들을 하나의 디렉토리에 몰아 넣습니다.
이후 pwsh 을 실행시키고, 다운로드 받은 연속지적도 zip 파일들이
모여있는 디렉토리로 이동하고 한방에 모두 압축해제를 합니다.
cd "연속지적도 zip 파일을 모아 놓은 경로";
ls "*.zip" | ForEach-Object {
Expand-Archive $_ ./
}
그러면 아래처럼 압축해제되어서 shp 파일들이 모두 한 디렉토리에 모입니다.
지저분해 보이지만 의도적으로 하나의 디렉토리에 모은 겁니다.
참고! 꼭 위처럼 압축해제를 해야되는 건 아닙니다.
예를 들어 아래처럼ShapeFile 이 들어있는 디렉토리
형태로 압축해제해도
정상적으로 아래 ps1 스크립트가 동작하도록 했습니다.심지어는 아래처럼 몇개는 디렉토리, 몇개는 외부에 ShapeFile 로 압축해제해도 동작합니다.
다음으로 아래와 같은 내용을 갖은 *.ps1
확장자를 갖는 파일을 생성합니다.
저는 스크립트 명을 import_multi_shape.ps1
이라고 짓겠습니다.
2024-02-15 변경사항!
- (개선 사항) 이제는
O...N Depth
의Shape File
을 재귀적으로 모두 찾아냅니다.- (버그 해결) 파라미터로 전달하는 모든 userid, password, host, port 값이 psql 에 적용됩니다.
param (
# DB Host 명 또는 IP
[string] $dbHost,
# DB Port 번호
[string] $dbPort,
# import DB 명
[string] $importDbName,
# import DB 스키마명
[string] $importSchema,
# postgresql 사용자 ID
[string] $dbConnectUserId,
# postgresql 사용자 비번
[string] $dbConnectUserPw,
# postgresql 에 partition table 을 모두 담을 논리테이블(Master 테이블 명)
[string] $partitionMasterTableNm,
# shape file 이 모두 담겨있는 디렉토리 경로. default: 스크립트 실행 디렉토리
[string] $shapeFileDirectoryPath = $(Get-Location));
<#
사용 예시 (ps1 스크립트명이 import_multi_shape.ps1 이라고 가정함)
.\import_multi_shape.ps1 `
-dbHost "localhost"`
-dbPort "5432" `
-importDbName "postgres" `
-importSchema "public" `
-dbConnectUserId "postgres" `
-dbConnectUserPw "postgres" `
-partitionMasterTableNm "lsmd_cont_ldreg" `
-shapeFileDirectoryPath "C:\Users\Desktop\연속지적도_2024"
#>
Write-Host "
You Had Insert Param Like Below...
"
Write-Host "dbHost=$dbHost"
Write-Host "dbPort=$dbPort"
Write-Host "importDbName=$importDbName"
Write-Host "importSchema=$importSchema"
Write-Host "dbConnectUserId=$dbConnectUserId"
Write-Host "dbConnectUserPw=$dbConnectUserPw"
Write-Host "partitionMasterTableNm=$partitionMasterTableNm"
Write-Host "shapeFileDirectoryPath=$shapeFileDirectoryPath"
Write-Host ""
if (!(Test-Path -PathType Container $shapeFileDirectoryPath)) {
throw "There is No Such Directory name [$shapeFileDirectoryPath]!";
}
elseif ((Get-ChildItem "$shapeFileDirectoryPath/*.shp").Length -eq 0) {
throw "There is no shape file in [$shapeFileDirectoryPath] directory";
}
# setting PGPASSWORD for using "psql -c" without password prompt.
$env:PGPASSWORD = $dbConnectUserPw;
# checking database connection.
psql -h "$dbHost" -p "$dbPort" -U "$dbConnectUserId" -d "$importDbName" -c "select 1";
# if connection state is bad it will stop the process
if($LASTEXITCODE -ne 0) {
throw "DataBase Connection Error Occured!!!"
}
# Drop table first and then create a new partition master table
Write-Host "REMOVING $importSchema.$partitionMasterTableNm table and create a new one";
# remove existing table
psql -h "$dbHost" -p "$dbPort" -U "$dbConnectUserId" -d "$importDbName" -c `
"drop table if exists $importSchema.$partitionMasterTableNm cascade;";
# creating new table.
psql -h "$dbHost" -p "$dbPort" -U "$dbConnectUserId" -d "$importDbName" -c `
"create table $importSchema.$partitionMasterTableNm (
sgg_oid integer,
jibun varchar(100),
bchk varchar(1),
pnu varchar(19),
col_adm_se varchar(5),
geom geometry(MultiPolygon, 5186)
) partition by range(pnu);";
Write-Host "
looping all the shapefiles inside '$shapeFileDirectoryPath'.
each shapefile will be imported as a parititon table
";
# loop all the shape file inside the directory you had config.
Get-ChildItem "$shapeFileDirectoryPath/*.shp" -Recurse | foreach-Object -ErrorAction Stop {
$shapeFileFullPath = $_;
$baseName = $_.BaseName;
$lastIndexOfUnderBar = $baseName.LastIndexOf('_');
# remove creation date Postfix from file name
$partitionTableNm = $baseName.Substring(0, $lastIndexOfUnderBar).ToLower();
# getting sido code which is middle of the shape file name
$sidoCode = $partitionTableNm.Substring($partitionTableNm.LastIndexOf('_') + 1);
# create pnu start range value
$pnuStart = $sidoCode.PadRight(19, '0');
# create pnu end range value
$pnuEnd = (([int16]$sidoCode) + 1).ToString().PadRight(19, '0');
Write-Host "importing `"$_`" file To `"$partitionTableNm`"
pnu range: [ '$pnuStart' ~ '$pnuEnd' ]";
# create partition table and attach to master table at once
psql -h "$dbHost" -p "$dbPort" -U "$dbConnectUserId" -d "$importDbName" `
-c "CREATE TABLE $importSchema.$partitionTableNm
PARTITION OF $importSchema.$partitionMasterTableNm
FOR VALUES FROM ('$pnuStart') TO ('$pnuEnd');";
# import shape file data to partition table.
ogr2ogr `
--config PG_USE_COPY YES -append -progress -f PostgreSQL `
PG:"host=$dbHost port=$dbPort dbname=$importDbName schemas=$importSchema user=$dbConnectUserId password=$dbConnectUserPw" `
"$shapeFileFullPath" `
--config SHAPE_ENCODING "EUC-KR" --config OGR_TRUNCATE YES `
-nln $partitionTableNm -nlt multipolygon;
}
Write-Host "
creating index for partiton master table $partitionMasterTableNm
";
# creating new index for "pnu"
Write-Host "EXECUTE: [psql -U $dbConnectUserId -c `"create index ${partitionMasterTableNm}_pnu_idx on $importSchema.$partitionMasterTableNm (pnu);`"]";
psql -h "$dbHost" -p "$dbPort" -U "$dbConnectUserId" -d "$importDbName" -c `
"create index ${partitionMasterTableNm}_pnu_idx
on $importSchema.$partitionMasterTableNm (pnu);";
# creating new gist index for geometry column
Write-Host "EXECUTE: [psql -U $dbConnectUserId -c `"create index $importSchema.${partitionMasterTableNm}_geom_idx on ${partitionMasterTableNm} using gist (geom);`"]";
psql -h "$dbHost" -p "$dbPort" -U "$dbConnectUserId" -d "$importDbName" -c `
"create index ${partitionMasterTableNm}_geom_idx
on $importSchema.${partitionMasterTableNm} using gist (geom);";
위 스크립트가 하는 일은 다음과 같습니다.
$partitionMasterTableNm
을 따릅니다.*.shp
을 재귀적으로 찾아내서 for loop
을 돕니다.*.shp
파일은 순회하면서 아래와 같은 상세 작업을 합니다.연월(ex: *_202311.shp)
표기를 지운 string 추출하고ogr2ogr
를 통해서 shp -> db table
로 데이터 COPY스크립트 파일을 생성하고 pwsh
에서 아래처럼 스크립트를 실행 시키면 됩니다.
실행 예시
.\import_multi_shape.ps1 `
-dbHost "localhost" `
-dbPort "5432" `
-importDbName "postgres" `
-importSchema "public" `
-dbConnectUserId "postgres" `
-dbConnectUserPw "root" `
-partitionMasterTableNm "lsmd_cont_ldreg" `
-shapeFileDirectoryPath "C:\shp_files\"
여기서 각 파라미터의 의미는 다음과 같으며, 자신의 상황에 맞게 세팅을 해야합니다.
dbHost="호스트 명칭 또는 IP";
dbPort="postgresql 서버 Port 번호";
importDbName="데이터 import 하려는 db 의 명칭";
importSchema="데이터 import 하려는 schema 의 명칭";
dbConnectUserId="postgresql 접속 사용자명";
dbConnectUserPw="postgresql 접속 비밀번호";
partitionMasterTableNm="생성하고자 하는 파티션 Master 테이블 명";
shapeFileDirectoryPath="shape 파일이 모여있는 Directory 경로"
이 값들은 자신의 상황에 맞게 수정해야겠죠?
스크립트 실행이 시작되면 아래와 같은 로그들이 보일 겁니다.
그리고 스크립트 실행이 끝난 후에 db 에서 테이블 상태를 보면...
👍👍