サクッと使うにはどこぞの外部サービスを利用するのが早いですが,訳あって自製することになりました.
利用できるデータだとか,dockerを使った環境構築とかを書き留めておきたいと思います.
システムの構成
http://server/rgeocode?lat=43.063751&lon=141.358524
のような形で緯度経度をパラメータとして渡すGETリクエストにより,住所の情報が入ったjsonがレスポンスボディとして返ってくるイメージです.
WebフロントエンドとしてAPIの機能を提供するためにFlaskを利用します.
住所データベースについてはPostgres(+PostGIS)を利用します.
docker-composeを利用してサーバの環境構築を行う
docker-composeを利用してサーバ環境を構築します.
docker-compose.ymlとAPIサーバをFlaskを利用して提供するためのpython3環境を構築するためDockerfileを用意しましたので下記に示します.
※postgres-postgisコンテナのDBアクセスユーザーやパスワードについては外部公開する場合などには適宜修正してください
version: '3'
services:
postgres-postgis:
container_name: postgres-postgis
image: mdillon/postgis
environment:
POSTGRES_DB: postgres
POSTGRES_USER: postgres
POSTGRES_PASSWORD: postgres
volumes:
- ./docker/db/data:/var/lib/postgresql/data
- ./docker/db/sql:/docker-entrypoint-initdb.d
- ./opt:/root/opt
ports:
- 5435:5432
python3env:
restart: always
build: python3
container_name: 'python3env'
working_dir: '/root/'
tty: true
volumes:
- ./opt:/root/opt
ports:
- 56743:56743
FROM python:3
USER root
RUN apt-get update
RUN apt-get -y install locales && \
localedef -f UTF-8 -i ja_JP ja_JP.UTF-8
ENV LANG ja_JP.UTF-8
ENV LANGUAGE ja_JP:ja
ENV LC_ALL ja_JP.UTF-8
ENV TZ JST-9
ENV TERM xterm
RUN apt-get install -y vim less
RUN pip install --upgrade pip
RUN pip install --upgrade setuptools
RUN pip install Flask
RUN pip install psycopg2
RUN pip install psycopg2-binary
コンテナの起動
下記のコマンドで行います.一度バックグラウンド起動させずに実行して,エラーが出なかったら一旦止めて再度バックグラウンド起動するのがスムーズ.
$ docker-compose up
(Ctrl+C)
$ docker-compose up -d
住所DBに利用する位置参照情報データの入手
住所DBについては国土交通省の位置参照情報データベースを利用して構築を行います.
(こういうすごいデータをタダで使えるんだから公共事業ってすごいよね)
https://nlftp.mlit.go.jp/isj/
にアクセスし左ペインの「位置参照情報 ダウンロード」を選択すると都道府県の選択画面が出ます.
今回は全国の街区単位のデータを集めるのでその手順について下記に示します.
- 「都道府県単位」を選択
- 遷移先の画面で「市区町村全域」にチェックを入れる
- 「全て」のラジオボタンを選択する
- データ整備年度は「平成29年」のものを選択した上で「選択」ボタンを桜花する
- ※平成30年も選択できるようだが2020年7月1日現在ではデータがない模様
- 「全ての街区レベルを選択」にチェックを入れて最下部までスクロールする
- 最下部に「選択」ボタンが有るので押下する
- 利用規約が表示されるのでよく読む
ファイルのダウンロード画面に遷移するので,47ファイルに対してを根気よく「ダウンロード」ボタンを押下してダウンロードする
住所DBの構築
そろそろ書くのが面倒になってきたのでコマンドと初期化コード載せます
postgresとpythonのコンテナを./opt(コンテナ内では/root/opt)に永続化してありますので,ここに先程ダウンロードした位置参照情報のzipファイル群を入れます.
# cd ./opt
$ chmod +x expandzip2csv.sh
$ ./download.sh
$ ./expandzip2csv.sh
$ docker-compose up -d
$ docker-compose exec postgres-postgis bash
$ mv ./opt/*-* /tmp/
$ cp ./opt/sample.sql /tmp
$ su postgres
$ psql -U postgres
$ postgres=> create database gis;
$ psql gis -U postgres
$ \i /tmp/sample.sql
位置参照情報のzipファイルを回答します.
# 解凍
find ./ -name "*-16.0a.zip" -type f -print0 | xargs -0 -I {} unzip {}
# 対象のcsvの改行コードを一括変換
find . -name '*.csv' -type f | xargs -n 10 nkf -Lu --overwrite
# 対象のCSV一覧を出力
find ./ -name "*.csv" -type f -print0 | xargs -0 -I {} echo `pwd`/{} > list.txt
データベースを構築していきます.
※postgresのコンテナに入ってpsql実行する.
$ psql -U postgres
$ postgres=>CREATE DATABASE gis;
$ postgres=>\q
$ psql gis -U postgres
-- 拡張をインストール
CREATE EXTENSION IF NOT EXISTS postgis;
-- テーブル削除
DROP TABLE IF EXISTS datalocation;
create table datalocation(
prefectures_nm varchar(100), -- 都道府県名
city_nm varchar(100), -- 市区町村名
oaza_nm varchar(100), -- 大字・丁目名
koaza_nm varchar(100), -- 小字・通称名
block_no varchar(100), -- 街区符号・地番
coordinate_no varchar(100), -- 座標系番号
x_coordinate numeric(8, 1), -- X座標
y_coordinate numeric(8,1 ), -- Y座標
latitude varchar(15), -- 緯度
longitude varchar(15), -- 経度
displayed_address_flg char(1), --住居表示フラグ
representative_flg char(1), -- 代表フラグ
before_history_flg char(1), -- 更新前履歴フラグ
after_history_flg char(1) -- 更新後履歴フラグ
);
SELECT AddGeometryColumn('datalocation', 'the_geom', 4612, 'POINT', 2);
update datalocationSET the_geom = ST_SETSRID(ST_POINT(longitude::float, latitude::float), 4612);
CREATE INDEX gist_loc_ref_info ON datalocation USING GIST (the_geom);
行数の確認.1900万行くらいありました.
SELECT count(*) from datalocation;
下記は東京タワーの緯度経度ですが,下記のSQL実行して東京タワーの住所がでてきたら成功です.
SELECT *
FROM datalocation
ORDER BY ST_SETSRID(ST_POINT(139.745433, 35.658581), 4326) <-> the_geom
LIMIT 1;
APIの構築を行う
ユーザーが逆ジオコーディングを行うために利用するAPIを構築します.
今回はpythonで動作するWebフロントエンドであるFlaskを利用します.
ソースベタ貼りします.
python3のコンテナ側からpostgresのコンテナに接続する際には,hostにコンテナ名を記載します.
from flask import Flask, request
import psycopg2
import json
app = Flask(__name__)
# SELECT *
# FROM m_address_gaiku
# WHERE representative_flg = 1
# ORDER BY ST_SETSRID(ST_POINT(139.745433, 35.658581), 4326) <-> the_geom
# LIMIT 1;
def getAddress(lon, lat):
users = 'postgres' #initial user
dbnames = 'gis'
passwords = 'postgres'
host = 'postgres-postgis'
port = '5432'
conn = psycopg2.connect(" user=" + users +" dbname=" + dbnames +" password=" + passwords +" host=" + host +" port=" + port)
cur = conn.cursor()
cur.execute("SELECT * FROM m_address_gaiku WHERE representative_flg = '1' ORDER BY ST_SETSRID(ST_POINT("+str(lon)+", "+str(lat)+"), 4612) <-> the_geom LIMIT 1;")
results = cur.fetchall()
#cur.fetchone()
#conn.commit()
cur.close()
conn.close()
return results
def generate_response(result):
ret = {
'status':200,
'result':
{
'prefecture': result[0][0],
'municipality': result[0][1],
'local' :
{
'section':result[0][2],
'homenumber':result[0][4],
'longitude':result[0][9],
'latitude':result[0][8]
}
},
'meta':[
{
'name':'thanks',
'content':'このサービスは 国土交通省 提供 街区レベル位置参照情報および大字・町丁目レベル位置参照情報 を利用しています'
}
]
}
return ret;
def generate_error_response(code):
ret = {
'status':code,
'result':
{
'prefecture': '',
'municipality': '',
'local' :
{
'section':'',
'homenumber':'',
'longitude':'',
'latitude':''
}
},
'meta':[
{
'name':'thanks',
'content':'このサービスは 国土交通省 提供 街区レベル位置参照情報および大字・町丁目レベル位置参照情報 を利用しています'
}
]
}
return ret;
@app.route('/')
def root():
ret = {}
try:
result = getAddress(139.745433, 35.658581)
ret = generate_response(result)
except:
ret = generate_error_response(201)
return json.dumps(ret)
# http://server/rgeocode?lat=43.063751&lon=141.358524
@app.route('/rgeocode', methods=['GET'])
def rgeocode():
lat = request.args.get('lat', '')
lon = request.args.get('lon', '')
ret = {}
try:
result = getAddress(lon, lat)
ret = generate_response(result)
except:
ret = generate_error_response(201)
return json.dumps(ret)
@app.errorhandler(400)
@app.errorhandler(404)
@app.errorhandler(500)
def error_handler(error):
response = {
"status":error.code,
"error":
{
"type": error.name,
"message": error.description
}
}
return json.dumps(response)
## おまじない
if __name__ == "__main__":
app.run(debug=True, host='0.0.0.0', port=56743)
参考
ソース一式はそのうちまとめてこちらにおいておきます
https://github.com/nc30mtd/docker-reverse-geocoding-pgsql