PostGIS

国土数値情報から行政区界を作成する

More than 3 years have passed since last update.

この記事とだいたい一緒です。

ここでは、住所コードからその市区町村の形状を描いてみます。流れとしては以下のようになります。

  1. 国土数値情報ダウンロードサービスから都道府県ごとのZIPファイルをダウンロード
  2. ZIPを展開し、Shape形式のデータをPostGISに登録
  3. PostGISを使って形状データを市区町村ごとに結合
  4. GeoJSONで出力して描画

まずは平成25年度のデータをダウンロードします。平成26年度のデータが公開されたらそちらを使った方が良いでしょう。クリックを繰り返すだけの単調作業なので、時間があるときに実施しておきます。47都道府県のZIPファイルを、どこか適当なディレクトリにまとめて保管します。

EC2インスタンスを起動

AWS の EC2 で作業します。インスタンスのイメージIDは Amazon Linux AMI のページに記載されています。その時の一番新しいタイプを使うと便利なことは多いですが、ライブラリのバージョン依存に悩む場合は、ちょっと古いタイプを試してみましょう。--user-data を使って自動アップデートを止める方法は下記の記事が参考になります。

PostGIS を yum でインストールする場合、EPEL の gdal-libs に依存します。
しかし、2014.03 のレポジトリだと EPEL の gdal-libs が依存する poppler のバージョンが合いません。 gdal-libspoppler-0.12 に依存しますが、最新のインスタンスには poppler-0.22.5 がインストールされています。ひとつ古い 2013.09 は poppler-0.12.4 がインストールされていますので、ソースコードからビルドするのが煩雑な場合はこちらを使うのが良いでしょう。

ということで、以下の構成で awscli のコマンドラインツールを使って EC2 を起動します。パッケージレポジトリのバージョンを固定しておきたいので、--user-data で起動時に自動アップグレードしないよう設定します。

種別 内容
AMI 2013.09 の 64bit
リージョン アジアパシフィック東京
イメージID ami-3561fe34
インスタンスタイプ t1.micro
SSH鍵 aws-tokyo-sandbox
セキュリティグループ SSHEnabledGroup
EC2インスタンスの起動
aws ec2 run-instances \
    --image-id ami-3561fe34 \
    --instance-type t1.micro \
    --count 1 \
    --key-name ec2-tokyo-sandbox \
    --security-groups SSHEnabledGroup \
    --user-data `cat << EOF | base64
#cloud-config
repo_upgrade: none
EOF`

SSH でログインしたらレポジトリの設定を変更します。

レポジトリ設定
curl -O http://yum.postgresql.org/9.3/redhat/rhel-6-x86_64/pgdg-redhat93-9.3-1.noarch.rpm
sudo rpm -ivh pgdg-redhat93-9.3-1.noarch.rpm
sudo vim /etc/yum.conf  # releasever=latest をコメントアウト
sudo vim /etc/yum.repos.d/pgdg-93-redhat.repo  # $releasever ⇒ latest

次に、PostGIS をインストールします。インストールが成功すると shp2pgsql コマンドが使えるようになっているはずです。

PostGISのインストール
sudo yum install postgresql93-server postgresql93-contrib -y
sudo yum install --enablerepo epel postgis2_93 postgis2_93_devel postgis2_93_utils -y

PostGIS がインストールできたら PostgreSQL を起動します。
作業を簡単に進めるために、アクセス権限を変更しておきましょう。SSHでログインしている場合はユーザー名を指定するだけにします。

データベース設定
sudo service postgresql-9.3 initdb
sudo service postgresql-9.3 start
sudo su - postgres
vi /var/lib/pgsql/9.3/data/pg_hba.conf    # 下記参照
/var/lib/pgsql/9.3/data/pg_hba.conf
local        all        all        trust

設定ファイルを変更したら PostgreSQL を再起動します。

sudo service postgresql-9.3 restart

データベースを作成し、PostGIS 拡張を有効にします。

データベース作成
createdb -U postgres geo
cat << EOF | psql -U postgres -d geo
CREATE EXTENSION postgis;
CREATE EXTENSION postgis_topology;
CREATE EXTENSION fuzzystrmatch;
CREATE EXTENSION postgis_tiger_geocoder;
SELECT postgis_full_version();
EOF

これでデータベースの設定ができました。

ZIPファイルの中身をPostGISに登録

国土数値情報ダウンロードサービスからダウンロードしたZIPファイルをEC2のインスタンスに転送します。転送できたら、ZIPファイルを展開して shp2pgsql でSQLファイルを作成し、PostGISに登録します。

for f in N03-*.zip
do
    unzip -d ${f%.zip} $f
done

for f in N03-*/*.shp
do
    t=`basename $f`
    sql=${t%.shp}.sql
    shp2pgsql -W "Shift_JIS" $f | psql -U postgres -d geo
done

ファイル名に応じたテーブルが生成されますので、47個のテーブルが生成されます。

データを見てみると、いくつかの都道府県のデータには注意が必要であることが分かります。 (北海道地方の支庁・地方事務所一覧 - Wikipedia)

北海道の支庁と振興局が混在している
SELECT distinct n03_002
FROM "n03-13_01_130401"
WHERE n03_002 IS NOT NULL
ORDER BY n03_002;

    n03_002
----------------
 上川支庁
 十勝支庁
 宗谷支庁
 後志支庁
 日高支庁
 根室振興局
 根室支庁
 檜山支庁
 渡島支庁
 留萌支庁
 石狩支庁
 空知支庁
 網走支庁
 胆振支庁
 釧路支庁
 釧路総合振興局
(16 rows)
香川県の郡・政令市名が香川県のレコードがある
\x

SELECT n03_001, n03_002, n03_003, n03_004, n03_007
FROM "n03-13_37_130401"
WHERE n03_001 = n03_003;

-[ RECORD 1 ]---
n03_001 | 香川県
n03_002 |
n03_003 | 香川県
n03_004 | 丸亀市
n03_007 | 37202
所属未定地の確認
SELECT n03_001, n03_002, n03_003, n03_004, n03_007 FROM "n03-13_13_130401" WHERE n03_007 IS NULL;

-- 結果は省略

たとえば、住所コードで集約したときに名称が不一致になるレコードを抽出すると、以下のレコードをリストアップできます。名寄せが必要なレコードには UPDATE 文を発行しておきましょう。

n03_001 n03_002 n03_003 n03_004 n03_007 レコード数
北海道 根室振興局 根室市 01223 76
北海道 根室市 01223 464
北海道 釧路支庁 厚岸郡 浜中町 01663 139
北海道 釧路総合振興局 厚岸郡 浜中町 01663 1
三重県 北牟婁郡 紀北町 24543 416
三重県 北牟婁郡 紀北町入会地 24543 110
香川県 香川県 丸亀市 37202 1
香川県 丸亀市 37202 14

市区町村には同一名称もありますので、集約させるキーには注意しましょう。また、テーブル定義で UNIQUE 制約を付ける場合は慎重に検討しましょう。

PostGISを使って形状データを市区町村ごとに結合

都道府県ごとのテーブルにあるレコードを住所コードで集約します。PostGIS では複数の形状データを ST_Union で結合できます。
今回の場合は、住所コード (n03_007 フィールド) で GROUP BY し、 geom フィールドを結合させます。変換先のテーブルは以下のように定義します。

変換先のテーブル定義
CREATE TABLE address (
    code varchar(5) PRIMARY KEY CHECK (length(code) IN (2, 5)),
    geom geometry(MULTIPOLYGON, 4326) NOT NULL
);

変換クエリはSQLファイルに保存し、 psql コマンドの "-v" オプションを使って変数をループさせます。都道府県ごとに別々のテーブルになっているためです。"table" にはテーブル名、"code" には都道府県コードを指定します。

transform.sql
INSERT INTO address
SELECT n03_007, ST_Union(ST_SetSRID(geom, 4326))
FROM :"table"
WHERE n03_007 IS NOT NULL
GROUP BY n03_007;

INSERT INTO address
SELECT :'code', ST_Union(ST_SetSRID(geom, 4326))
FROM :"table"
WHERE n03_007 IS NULL
GROUP BY n03_001;
都道府県ごとにクエリを実行
for i in {01..47}
do
    psql -U postgres -d geo -f transform.sql -v table="n03-13_${i}_130401" -v code=$i
done

GeoJSONで出力して描画

PostGIS の ST_AsGeoJSON を使うと形状データを GeoJSON で出力できます。
市区町村ごとにファイルを作成してみましょう。

echo "SELECT code, ST_AsGeoJSON(geom) FROM address" | psql -U postgres -d geo -t -A --field-separator=$'\t' > address.txt
awk '{ print $2 >> $1".geojson" '} address.txt

出来上がった GeoJSON ファイルを geojson.io に貼付けてみると、市区町村の形状を確認できます。自前で描画したい場合は Leaflet や D3 を使って JavaScript を書けば良いでしょう。

住所コードに対応する名称は総務省の全国地方公共団体コードで定義されています。利用する住所データに対応する年度のコード表を使うとマッピングできます。なお、いくつか注意点があります。

  • 年度が異なる場合は市区町村の合併などに適応できない場合があります。
  • 総務省のデータには6桁目のチェックディジットがあります。
  • 総務省のデータには「郡」がありません。
  • 総務省のデータは都道府県市区町村名はカタカナですが、政令指定都市名はひらがなで併記されています。

PostgreSQL などのデータベースでデータを管理しておくと、JOIN を使った結合処理を簡単に記述できる点は便利だと言えます。表示するラベルを工夫したい場合はデータを出力するクエリを頑張って記述しましょう。