日本郵便が公開している郵便番号CSVファイルを使用し、CouchDBを使ったアプリケーションの具体例として郵便番号検索システムを作成しています。
コードなどはまとめて公開したいとは思っていますが時期は未定です。
断片的な成果についてはBlogに反映させています。
バックエンドのライブラリをYALToolsベースに変更しています。
その他のリリース後の修正点は「変更履歴」をご覧下さい。
当システムはデータベースエンジンにApache CouchDBを使用した郵便番号の検索システムです。
このシステムはCouchDBの実証テストを主な目的としており、使い勝手などについては他の郵便番号検索システムとは異なる部分があります。
郵便番号から住所を検索する他に、全国から同名の市町村を検索するなど、郵便番号データベースをいくつかの角度から検索できるようにしています。
また mode=search_json をリクエストに追加することで、理時間などのフィールドを追加したCouchDBが出力するJSON形式で返す事もできます。
データは郵便番号情報を元にし、前方一致で検索する仕組みになっている都合上、複数の地域が登録されている場合には、検索結果に表示されない場合があります。 (例:921-8046 「法師山、坊山、マ、鱒川淵、ム、元末、元涌波庚、ヤ、リ、ル、レ乙、」の場合、途中の「ム」などを入力しても結果に表示されません)
郵便番号の前3桁と後4桁を入力した場合、その他の検索項目を条件に加えることはできません。
また郵便番号(前3桁、又は、後4桁)を含めて3つ以上の項目を条件とする事はできません。 3つの検索条件は「都道府県」、「市区郡」、「町村」の組み合せについてのみ行なえます。
現時点では2つまでの項目について、ほぼ全ての組み合せで検索ができるようになっています。
本システムで利用しているライブラリは次のとおりです。
利用にあたって修正したコードについては、Blog等で解説しているか、する予定です。
検索条件の入力欄の上段には半角数字、下段にはカタカナ、あるいは漢字で条件を入力し、検索する事ができます。
下記は郵便番号の前3桁を入力し、195件の結果が得られた事を示しています。
下記は2つの条件を入れることで、福島県の「カミ」で始まる郵便番号の割り当てられた地域が61件ある事を示しています。
検索用の入力欄は全部で6つあります(「表示件数」は出力の調整用です)が、 最大3つの入力を組み合せて検索する事ができます。
設計時に想定している組み合せは次の通りです。 "on"は何かしらの文字列が入力されている事を示しています。 "on*"は、入力された文字列が(「福島」などの)一部分ではなく(「福島県」のように)、郵便番号データベースに登録されている内容と完全にマッチする必要があります。
CouchDB内部では、この組み合せそれぞれについて対応するViewを定義しています。
No. | 郵便番号5桁 | 郵便番号 前3桁 | 郵便番号 後4桁 | 都道府県 | 市区郡 | 町村、その他 |
---|---|---|---|---|---|---|
1 | on | |||||
2 | on* | on | ||||
3 | on* | off | ||||
4 | off | on | ||||
5 | on | on* | ||||
6 | on* | on | ||||
7 | on* | on | ||||
8 | off | on | on* | |||
9 | off | on* | on | |||
10 | off | on* | on | |||
11 | off | off | on | |||
12 | on | off | off | |||
13 | off | on | off | |||
14 | on* | off | on | |||
15 | on* | on | off | |||
16 | off | on* | on | |||
17 | on* | on* | on |
複数項目を入力する場合には、左側の項目については完全に一致する名称を入力する必要があります。 JavaScriptを有効にしている場合には、補完機能を利用する事ができます。
複数欄へ入力した場合の優先順位については「検索方法::サポートしている入力の組み合せ」にあります。
例えば県名に「福シマ」のように、カナと漢字を混在させると検索する事ができません。
フィールドが違う場合、「福島県」、「アイヅ」のような2入力を検索する事は可能です。
3つの入力項目を選択する場合には、「全て漢字」、あるいは 「全てカナ」でないと検索する事はできません。
カナ、漢字を混ぜて「都道府県名」、「市区郡名」、「町村名、その他の地名」の3つの項目を入力した場合には、全てが空欄の場合と同様に郵便番号が「"965"で始まる条件」での検索結果が表示されます。
「都道府県名」、「市区郡名」、「町村名、その他の地名」についてはカナ、漢字での入力内容に応じて候補が動的に表示されます。
「町村名、その他の地名」の補完の際には、「都道府県名」、「市区郡名」など他の欄に入力された内容を参考に候補を表示します。 両方の欄に入力されていた場合には、市区郡名の内容を優先して補完を行ないます。
「市区郡名」についても同様に、「都道府県名」に存在する名称が候補として表示されます。
「都道府県名」については、常にカタカナ、漢字での全候補が表示されます。
複数の項目を入力する場合に、右側の欄に入力する内容は完全一致する名称である必要があり、名称の一部である場合には検索が正常に行なわれない場合があります。
入力補完について挙動がおかしくなった場合には入力欄右上の「全入力項目のリセット」のリンクをたどって先頭画面に移動してください。
Ver. 1.6から多くのパラメータ名が変更されています。
処理は全てGETリクエストとパラメータの組み合せにより行なわれています。
パラメータの指定によりJSON形式で出力を取り出す事ができます。
典型的なURLは次のようなものです。
http://www.yadiary.net/postal/main.fcgi?mode=search_json&page=1&unit=5&pref=%E7%A6%8F%E5%B3%B6%E7%9C%8C&city=%E4%BC%9A%E6%B4%A5%E8%8B%A5%E6%9D%BE%E5%B8%82&street=%E3%82%A4%E3%83%B3%E3%82%BF%E3%83%BC%E8%A5%BF
Ver. 1.6時点での出力に影響のあるパラメータは次の通りです。 前2つは画面に表示する出力件数の調整のために使います。
pref_js, city_js, street_jsパラメータはFlexBox用のパラメータで、内部ではpref, city, streetに値を代入しています。 ブラウザでは2つのパラメータが指定されているようにみえますが、省略可能です。
他のスクリプトから参照する場合に、全ての検索結果を読み込みたい場合にはunitに指定する値を全件数の122977を越える数字を指定する事で"page"の指定を事実上無視し、全データを一度にダウンロードする事ができます。
いまのところデータ形式について詳細は決めていませんが、Ver. 1.6時点での内部構造は次のようになっています。
{ "rows":[ { "_id": "90fd89479139d6f3020fe1f9b1d6e80691dda64f", "_rev": "1-75a3aba38cd6d0508c0ead9dd9c63e1e", "type": "pcode", "x0401": "全国地方公共団体コード", "p": "都道府県 (漢字)", "pk": "都道府県 (カタカナ)", "c": "市区郡名 (漢字)", "ck": "市区郡名 (カタカナ)", "s": "町村名、その他の地名 (漢字)", "sk": "町村名、その他の地名 (カタカナ)", "code": "郵便番号 7桁", "ocode": "旧郵便番号 5桁", "codep": "郵便番号 前3桁", "codes": "郵便番号 後4桁", "op1": "一町域が二以上の郵便番号で表される場合の表示", "op2": "小字毎に番地が起番されている町域の表示", "op3": "丁目を有する町域の場合の表示", "op4": "一つの郵便番号で二以上の町域を表す場合の表示", "op5": "更新の表示(注6)(「0」は変更なし、「1」は変更あり、「2」廃止(廃止データのみ使用))", "op6": "変更理由 (「0」は変更なし、「1」市政・区政・町政・分区・政令指定都市施行、「2」住居表示の実施、「3」区画整理、「4」郵便区調整等、「5」訂正、「6」廃止(廃止データのみ使用))" } ], "rows_total": 表示件数, "total": 合計件数, "elapsed_time": 処理時間 }
Ver. 1.6.からCouchDBの内部形式を変更しました。 現状では従来との互換性を保持せずに、そのままのデータを表示しています。
その他、JSON形式を要求した場合に返す典型的なレスポンスは次の通りです。
json=true出力時のヘッダは Content-Type: application/json; charset=UTF-8 で、可読性を考慮して最後に改行を含んでいます。
出力されたJSON形式は「JSONクライアント例」のアプリケーションで処理できることを確認しています。
JavaScriptのXMLHttpRequestから扱うために、XML出力を追加しました。
リクエストに使うクエリの組み立てと各項目の意味については、「Queryパラメータ / JSON出力」を参照してください。
データ形式はJSON出力と、ほぼ同じです。 XML出力に合わせるために、"_id", "_rev"タグについては、XMLに合わせてアンダースコアを省いた"id", "rev"に修正しています。
実際の出力は次の通りです。
<?xml version="1.0"?> <postal_search_results> <total_rows>5</total_rows> <total>170</total> <reason>ok</reason> <elapsed_time>0.051</elapsed_time> <row> <id>90fd89479139d6f3020fe1f9b1d6e80691dda64f</id> <rev>1-75a3aba38cd6d0508c0ead9dd9c63e1e</rev> <type>pcode</type> <x0401>07202</x0401> <p>福島県</p> <pk>フクシマケン</pk> <c>会津若松市</c> <ck>アイヅワカマツシ</ck> <s>以下に掲載がない場合</s> <sk>イカニケイサイガナイバアイ</sk> <code>9650000</code> <ocode>965</ocode> <codep>965</codep> <codes>0000</codes> <op1>0</op1> <op2>0</op2> <op3>0</op3> <op4>0</op4> <op5>0</op5> <op6>0</op6> </row> </postal_search_results>
出力時のヘッダは Content-Type: text/xml; charset=UTF-8 で、可読性を考慮して最後に改行を含んでいます。
出力結果はGoogle Chrome ExtensionからXMLHttpRequestを通してアクセスできる事を確認しています。 完成次第、GitHub,あるいは, Gitoriousにて公開する予定です。
Python 2.6.5 [GCC 4.4.3] on linux2 を使った例です。
本体のmain.pyとライブラリのyapostal.rbの2つのファイルで構成しています。
まずはmain.py部分です。
#!/usr/bin/python
# -*- coding: utf-8 -*-
import httplib
import json
import sys
if len(sys.argv) != 3:
print "Usage: %s <code_prefix> <code_suffix>" % (sys.argv[0])
print " code_prefix: first three digits"
print " code_suffix: last four digits, but clipped number is also ok."
print ""
print " e.x.) %s 382 002" % (sys.argv[0])
sys.exit(1)
pass
from yapostal import *
yap = YaPostal()
yap.setCodePrefix(sys.argv[1])
yap.setCodeSuffix(sys.argv[2])
yap.setUnit(10)
yap.setPage(0)
for row in yap.getEachRow():
print("%(codep)s-%(codes)s %(p)s %(c)s %(s)s" % row)
pass
sys.exit(0)
ライブラリのyapostal.pyの部分です。同じディレクトリに配置してください。
import httplib
import json
class YaPostal():
def __init__(self, host = 'stage.yadiary.net', port = 80):
self.conn = httplib.HTTPConnection('%s:%d' % (host, port))
self.query = {'mode':'search_json'}
pass
def setQuery(self, key, value):
self.query[key] = value
pass
def setCodePrefix(self,code_prefix):
self.setQuery('code_prefix', code_prefix)
pass
def setCodeSuffix(self,code_suffix):
self.setQuery('code_suffix', code_suffix)
pass
def setUnit(self, unit):
self.setQuery('unit', unit)
pass
def setPage(self,page):
self.setQuery('page', page)
pass
def getRows(self):
query = ""
for k,v in self.query.iteritems():
if query != "":
query += "&%s=%s" % (k,v)
else:
query += "%s=%s" % (k,v)
pass
pass
self.conn.request("GET", "/postal/main.fcgi?" + query)
self.query['page'] += 1
r1 = self.conn.getresponse()
return json.loads(r1.read())
def getEachRow(self):
count = 0
j = self.getRows()
max = int(j['total'])
while max > count:
for row in j['rows']:
yield row
pass
count += len(j['rows'])
j = self.getRows()
pass
pass
pass
出力例は次の通りです。
$ ./main.py 382 0022 382-0022 長野県 須坂市 豊丘上町
Created: 2010-12-04, Last modified: 2012-05-06