郵便番号から都道府県を調べる

PHPで郵便番号から都道府県を導く処理が必要になりました。

郵便番号から住所を調べるには、日本郵便で公開されているCSVをDBに突っ込んで、クエリを投げるのですが、データサイズもでかいし、市区町村までは必要がないのでとりあえず都道府県だけを簡単に調べる方法がないか探していました。

適当に調べていたところ、郵便番号の先頭2桁を見ればいいだろうという記事が多いものの、一部の都道府県は他県に混ざっているものがあるらしいく、それに対応したコードが見つからなかったので、日本郵便の検索と格闘しながら新しくコードを用意することにしました。

で、以下のようになりました。
とりあえず動けばいいので、処理速度の最適化は確認していません。

/*==============================================================================
 郵便番号から、都道府県番号を取得する簡易版

	要素:
		郵便番号(nnn-nnnn or nnnnnnn)
	戻り値:
		都道府県番号

	※ 備考:
		1 	北海道
		2 	青森県
		3 	岩手県
		4 	宮城県
		5 	秋田県
		6 	山形県
		7 	福島県
		8 	茨城県
		9 	栃木県
		10 	群馬県
		11 	埼玉県
		12 	千葉県
		13 	東京都
		14 	神奈川県
		15 	新潟県
		16 	富山県
		17 	石川県
		18 	福井県
		19 	山梨県
		20 	長野県
		21 	岐阜県
		22 	静岡県
		23 	愛知県
		24 	三重県
		25 	滋賀県
		26 	京都府
		27 	大阪府
		28 	兵庫県
		29 	奈良県
		30 	和歌山県
		31 	鳥取県
		32 	島根県
		33 	岡山県
		34 	広島県
		35 	山口県
		36 	徳島県
		37 	香川県
		38 	愛媛県
		39 	高知県
		40 	福岡県
		41 	佐賀県
		42 	長崎県
		43 	熊本県
		44 	大分県
		45 	宮崎県
		46 	鹿児島県
		47 	沖縄県

 2011-06-06 新設
------------------------------------------------------------------------------*/
function _prefecture_from_zip( $zip ){

	     if( preg_match( '/^01/',     $zip ) ){ return  5; } // "秋田県";   }
	else if( preg_match( '/^02/',     $zip ) ){ return  3; } // "岩手県";   }
	else if( preg_match( '/^03/',     $zip ) ){ return  2; } // "青森県";   }
	else if( preg_match( '/^0[4-9]/', $zip ) ){ return  1; } // "北海道";   }
	else if( preg_match( '/^1[0-9]/', $zip ) ){ return 13; } // "東京都";   }
	// ^20は欠番
	else if( preg_match( '/^2[1-5]/', $zip ) ){ return 14; } // "神奈川県"; }
	else if( preg_match( '/^2[679]/', $zip ) ){ return 12; } // "千葉県";   }
	// ^28は欠番
	else if( preg_match( '/^3[01]/',  $zip ) ){ return  8; } // "茨城県";   }
	else if( preg_match( '/^32/',     $zip ) ){ return  9; } // "栃木県";   }
	else if( preg_match( '/^3[3-6]/', $zip ) ){ return 11; } // "埼玉県";   }
	else if( preg_match( '/^37/',     $zip ) ){ return 10; } // "群馬県";   }
	else if( preg_match( '/^3[89]/',  $zip ) ){ return 20; } // "長野県";   }
	else if( preg_match( '/^40/',     $zip ) ){ return 19; } // "山梨県";   }
	else if( preg_match( '/^4[1-3]/', $zip ) ){ return 22; } // "静岡県";   }
	else if( preg_match( '/^4[4-9]/', $zip ) ){ return 23; } // "愛知県";   }
	else if( preg_match( '/^50/',     $zip ) ){ return 21; } // "岐阜県";   }
	else if( preg_match( '/^51/',     $zip ) ){ return 24; } // "三重県";   }
	else if( preg_match( '/^520\-?046[1-5]$/', $zip ) ){ return 26; } // "京都府";   } // 特殊
	else if( preg_match( '/^52/',     $zip ) ){ return 25; } // "滋賀県";   }
	else if( preg_match( '/^5[3-9]/', $zip ) ){ return 27; } // "大阪府";   }
	else if( preg_match( '/^6[0-2]/', $zip ) ){ return 26; } // "京都府"; }
	else if( preg_match( '/^630\-?027[12]$/',  $zip ) ){ return 27; } // "大阪府"; } // 特殊
	else if( preg_match( '/^63/',     $zip ) ){ return 29; } // "奈良県"; }
	else if( preg_match( '/^64/',     $zip ) ){ return 30; } // "和歌山県"; }
	else if( preg_match( '/^6[5-7]/', $zip ) ){ return 28; } // "兵庫県"; }
	else if( preg_match( '/^68/',     $zip ) ){ return 31; } // "鳥取県"; }
	else if( preg_match( '/^69/',     $zip ) ){ return 32; } // "島根県"; }
	else if( preg_match( '/^7[01]/',  $zip ) ){ return 33; } // "岡山県"; }
	else if( preg_match( '/^7[23]/',  $zip ) ){ return 34; } // "広島県"; }
	else if( preg_match( '/^7[45]/',  $zip ) ){ return 35; } // "山口県"; }
	else if( preg_match( '/^76/',     $zip ) ){ return 37; } // "香川県"; }
	else if( preg_match( '/^77/',     $zip ) ){ return 36; } // "徳島県"; }
	else if( preg_match( '/^78/',     $zip ) ){ return 39; } // "高知県"; }
	else if( preg_match( '/^79/',     $zip ) ){ return 38; } //"愛媛県"; }
	else if( preg_match( '/^8[0-3]/', $zip ) ){ return 40; } // "福岡県"; }
	else if( preg_match( '/^84/',     $zip ) ){ return 41; } // "佐賀県"; }
	else if( preg_match( '/^85/',     $zip ) ){ return 42; } // "長崎県"; }
	else if( preg_match( '/^86/',     $zip ) ){ return 43; } // "熊本県"; }
	else if( preg_match( '/^87/',     $zip ) ){ return 44; } //"大分県"; }
	else if( preg_match( '/^88/',     $zip ) ){ return 45; } // "宮崎県"; }
	else if( preg_match( '/^89/',     $zip ) ){ return 46; } // "鹿児島県"; }
	else if( preg_match( '/^90/',     $zip ) ){ return 47; } // "沖縄県"; }
	else if( preg_match( '/^91/',     $zip ) ){ return 18; } // "福井県"; }
	else if( preg_match( '/^92/',     $zip ) ){ return 17; } // "石川県"; }
	else if( preg_match( '/^93/',     $zip ) ){ return 16; } // "富山県"; }
	else if( preg_match( '/^9[45]/',  $zip ) ){ return 15; } // "新潟県"; }
	else if( preg_match( '/^9[67]/',  $zip ) ){ return  7; } // "福島県"; }
	else if( preg_match( '/^98/',     $zip ) ){ return  4; } // "宮城県"; }
	else if( preg_match( '/^99/',     $zip ) ){ return  6; } // "山形県"; }
	return NULL;

}

突込みがある人は遠慮なくどうぞ。
とりあえず、これで一つ機能が追加できたので、でめたしでめたし。

TCPDFでOTFフォントを使用する

PHPでPDFを扱うためのライブラリにTCPDFがあります。
TCPDFでOTFフォントを使用すると、AcrobatReaderでは正常に読めても、Illustratorで正常に読めず、テキストがラスタライズされて少々気持ちの悪い事態に遭遇することとなります。
Illutratorで開くことを想定しているなら、TCPDFで扱うフォントには少々の工夫が必要なので、ここに残しておきます。

まず、TCPDFで扱うフォントはTTFフォントで統一します。フォント名が同じであればTTFで作成したPDFでも、OTFフォントがインストールされた環境のIllustratorでも問題なく読むことが出来ます。

OTFフォントをTTFフォントに変換するにはFontForgeを使用します。
インストール方法を検索すると、Windowsのcygwin環境下でのインストール方法が沢山ヒットしますが、個人的にはうまくいかなかったので、LinuxのCentOS5を使用しました。アプリケーションはGUIでx11環境が必要なので、デスクトップ版を用意しましょう。
パッケージはhttp://sourceforge.net/projects/fontforge/files/fontforge-executables/から「fontforge-20090923-1.i386.rpm」をダウンロードします。少々古いものですが、特に問題ありません。ダウンロードが終わると、ファイルをダブルクリックするだけでインストールが始まり、あっさりと完了します。

アプリケーションはGUIなのですが、メニューには登録されませんのでファイルを直接起動するか、ターミナルから起動する必要があります。ターミナルで「fontforge」と入力して起動すると、読み込むファイルを要求されるのでTTFフォントに変換したいOTFフォントを選択します。

選択したOTFファイルの内容によってはCIDマップファイルが必要と言われるかもしれません。CIDマップファイルはhttp://fontforge.sourceforge.net/cidmaps.tgzにありますので、ダウンロードして展開したファイルを「/usr/share/fontforge/」にコピーします。

しばらくして読み込みが完了すると文字コード表が表示されるので、メニューより「CID>単一化」を選択してファイルを統合します。
そしてメニューより「ファイル>フォントを保存」を選択して、保存形式を「TrueType」に設定し、保存ファイル名を入力して保存すれば完了です。
これで変換作業は完了です。作成されたTTFファイルを開いて正常に読み込むことができれば成功です。

フォントファイルをTCPDFで扱える状態にする方法は、ライブラリ内のtcpdf/fonts/utils/README.TXTにある通りで問題ありません。むしろ他に方法がありません。
「$ ttf2ufm -a -F myfont.ttf」
「$ php -q makefont.php myfont.ttf myfont.ufm」
これで作成されたファイルをfontsディレクトリにコピーし、TCPDFよりPDFを作成すればAcrobatReaderでもIllustratorでも問題なく開くことが出来るPDFファイルの完成です。

いまいち文章が推敲されていないのでまとめます。

TCPDFはPDF作成ライブラリですごい
TCPDFのPDFはOTFフォントを使うとIllustratorでは読めない(ラスタライズされる)ことがある
TCPDFのPDFをIllustratorで読む可能性があるならOTFフォントをTTFフォントに変換しておく
OTFフォントをTTFフォントに変換するにはFontForgeを利用する
CIDマップがないとOTFファイルを展開できない(ことがあるかもしれない)

TCPDF5.8.034のフォント指定の不具合

PHPでPDFを扱うにはまずTCPDF一択なわけですが、バージョンをよく見ると4.6.026という3年ぐらい前のソースなので新しくしようと思い立ちました。
動いていたので特に問題はないのですが、これだけ古いとどうかなという感覚的なものです。
最新版の5.8.034を入れて、動作を確認したところ、特に問題なし。
次のステップとして自前で用意したIPAフォントを指定したところ、処理が戻ってこないままタイムアウトになりました。
古い4.6に戻すと正常に出力されます。
新しい5.8にするとタイムアウトします。
だめだこりゃ。
別のバージョンで試すしかない。
どのミラーも最新版に更新されていて、最新版しか置いてない。
あちこち探し回った結果、発見したのが
http://ftp.heanet.ie/disk1/sourceforge/t/project/tc/tcpdf/OldFiles/
これで1つづつ遡りながら、動くバージョンを選ぶ作業に移ります。

時間をかけてマイナーバージョンごとに動作を確認して言ったのですが、どうも5.2から実装されたらしいサブセット埋め込みで不具合が発生している模様。
解説サイトでは5.4で解消されたと書いてあるけれど、検証していないみたいで実際に走らせるとタイムアウトになってしまう。
時間を大きくとれば言いなんて書いてあるけど、レスポンスが分単位なんて実際問題ありえないし、これは実装の不具合としか言いようがない。
結局、5.2の前のバージョン、5.1.002を導入してシステムを構築することにしました。

Windows7にもてあそばれる

一通り入れた後に、MySQLを入れ忘れたのに気がついて入れてみたところ、PHPからアクセスできない。実際にはphpMyAdmin3からアクセスできない。エラーメッセージも表示されず、1分ぐらい待たされて方真っ白な画面になってしまう。
コマンドからは問題なくアクセスできるものの、mod_phpからアクセスできない。
このときのPHPは5.3.3、MySQLは5.1.50(64bit)でした。
httpd.confやphp.iniやmy.iniをいじっては見たものの改善せず。
ここで不貞寝しました。

そして翌日。
まず最初にMySQLをノーマルからエッセンシャルに入れ替えたり、32bitにしたりバージョンを入れ替えたりしたけれどうまくいかない。PHPを入れる際にVCのライブラリを更新したのでそれに問題があるのではと考え、仕方がなくWindows7を入れなおすことに。

とりあえずWindows7を上書きでインストールしなおしてみたところ、前のシステムが複製されて残っていて、とっても気持ちが悪い状態に。仕方がないので改めてパーティションをフォーマットしなおしてから、改めて入れなおすことに。

まず最初にMySQL5.1.50(64bit)を入れて、次にApache2.2.16、そしてVCを更新せずにPHP5.3.3を入れなおしてみたところ、やはり現象は同じ。
ここでふとPHPのインストーラーにVCの対象を選択する新しい項目があったのを思い出して、前のバージョンで試してみようと思い立ちました。
PHP5.2.14を入れなおしたところ、zlib.dllがないといわれるのでネットから拾ってきて放り込んだところ、難なく起動。

問題だったのはPHP5.3.3.
データベースとの連携が高いはずのPHPで、このような障害が発生しました。
何でもかんでも組み込み関数にしてしまったPHPの成れの果てを垣間見た気がしました。

結局それ以外では、ActivePerlを64bitにするとMinGWと連携できないようなので、32bitで入れなおしたぐらいで、特に大きな問題もなく開発環境は整いました。
あとはデータを移していくだけです。これもかなり面倒なのですが。

PHPのセーフモードに悶え苦しむ

PHPの開発はなんだかんだで自前鯖や自宅鯖だったので、モジュールでフルの状態で動かしていたわけです。
プリントライはcoreserverに乗せたのですが、ここはセーフモードなのです。
あらかじめわかってはいましたがここまで時間をとられるとは思いませんでした。
まぁ、なんだかんだでCGIモードでなければならない部分はAjaxで逃げて事なきを得ています。
とりあえず行き詰った部分を。

・execが使えない(セーフモードというよりは鯖の設定か)
・mail関数で第5引数があるとワーニング8セーフモードのせい)
・ディレクトリのオーナーが違うと書き込めない(セーフモードのせい・Apacheとphpのユーザ)

PHPのGDで文字を描く

PHPで画像を扱うにはGDが楽なのですが、文字を書こうとすると思ったポイントで描画されません。
おそらくDPIを設定しないとだめなのでしょうが、PHPのリファレンスを探しても見つかりませんでした。
仕方がないのでいろいろ値をとりながら調べてみると、96dpiということが判明。
webではなくアプリケーション向けに72dpiで画像を生成するには以下のような手順となった。
ちなみに画像からはみ出にくいように調整もしてある。

$fontSize = 72;
$dpi = 96;
$margin = $fontSize / 8;
$baseline = $fontSize + $margin;
$size = $fontSize + $margin * 2;

$image = imagecreatetruecolor( $size, $size );

imageantialias( $image, true );
imagealphablending( $image, false );
imageSaveAlpha( $image, true );

$fillcolor = imagecolorallocatealpha( $image, 0, 0, 0, 127 );
imagefill( $image, 0, 0, $fillcolor );

$black = imagecolorallocate( $image, 0, 0, 0 );

$text = mb_convert_encoding( "あ", 'utf8' );
$font = 'xxxx.otf'; // 適当に置き換えて
imagettftext( $image, $fontSize/$dpi*$fontSize, 0, 0, $baseline, $black, $font, $text );

imagepng( $image, 'output.png', 9 );
imagedestroy( $image );

印刷も考えるとEPSで描いてから画像にするのが一番確実だけど、imagemagic(正確にはghostscript)を経由するのも面倒だしこうなった。

PHPにPEAR::SOAPをインストールする

CentOS5.3での話です。
SOAPが必要になったのでPEARからインストールすることにしました。
PHP5ではあらかじめ用意されているみたいだけど、公式にすらまともな解説がないのであきらめました。
pear install SOAP-beta
Did not download optional dependencies: pear/Mail, pear/Mail_Mime, pear/Net_DIME, use –alldeps to download automatically
pear/SOAP requires PEAR Installer (version >= 1.5.4), installed version is 1.4.9
pear/SOAP can optionally use package “pear/Mail”
pear/SOAP can optionally use package “pear/Mail_Mime”
pear/SOAP can optionally use package “pear/Net_DIME”
No valid packages found
install failed
どうやら、インストーラーが古いのと、いろいろと足らないものがあるので–alldepsスイッチをつけろとのこと。

pear install --alldeps SOAP-beta
pear/SOAP requires PEAR Installer (version >= 1.5.4), installed version is 1.4.9
pear/Mail_Mime requires PEAR Installer (version >= 1.6.0), installed version is 1.4.9
pear/Mail_mimeDecode requires PEAR Installer (version >= 1.6.0), installed version is 1.4.9
pear/Mail_mimeDecode requires package "pear/Mail_Mime" (version >= 1.4.0, excluded versions: 1.4.0)
downloading Mail-1.2.0b2.tgz ...
Starting to download Mail-1.2.0b2.tgz (21,972 bytes)
........done: 21,972 bytes
downloading Net_DIME-1.0.1.tgz ...
Starting to download Net_DIME-1.0.1.tgz (7,535 bytes)
...done: 7,535 bytes
downloading Net_SMTP-1.3.3.tgz ...
Starting to download Net_SMTP-1.3.3.tgz (10,944 bytes)
...done: 10,944 bytes
downloading Auth_SASL-1.0.3.tgz ...
Starting to download Auth_SASL-1.0.3.tgz (5,724 bytes)
...done: 5,724 bytes
install ok: channel://pear.php.net/Auth_SASL-1.0.3
install ok: channel://pear.php.net/Net_SMTP-1.3.3
install ok: channel://pear.php.net/Net_DIME-1.0.1
install ok: channel://pear.php.net/Mail-1.2.0b2


まだ何か文句を言っているので、PEARをアップグレードしてみる

pear upgrade PEAR
downloading PEAR-1.9.0.tgz ...
Starting to download PEAR-1.9.0.tgz (291,634 bytes)
.............................................................done: 291,634 bytes
upgrade ok: channel://pear.php.net/PEAR-1.9.0
PEAR: Optional feature webinstaller available (PEAR's web-based installer)
PEAR: Optional feature gtkinstaller available (PEAR's PHP-GTK-based installer)
PEAR: Optional feature gtk2installer available (PEAR's PHP-GTK2-based installer)
To install use "pear install pear/PEAR#featurename"


もう一度SOAPを入れなおしてみる

pear install -f --alldeps SOAP-beta
WARNING: "pear/HTTP_Request" is deprecated in favor of "pear/HTTP_Request2"
downloading SOAP-0.12.0.tgz ...
Starting to download SOAP-0.12.0.tgz (71,233 bytes)
.................done: 71,233 bytes
install ok: channel://pear.php.net/SOAP-0.12.0


まだしつこく何か言ってるけど、疲れたので放置。

PHPのPEARで覚書

CENTOS5.3での話です。
PEARをアップグレードしようとすると以下のように怒られました。

pear upgrade PEAR
WARNING: channel "pear.php.net" has updated its protocols, use "channel-update pear.php.net" to update
pear/Archive_Tar requires PEAR Installer (version >= 1.5.4), installed version is 1.4.9
pear/PEAR dependency package "pear/Archive_Tar" installed version 1.3.1 is not the recommended version 1.3.3, but may be compatible, use --force to install
No valid packages found
upgrade failed


PEARが古いのと、Archive_Tarが古いといわれてしまいました。

pear channel-update pear.php.net
Retrieving channel.xml from remote server
Update of Channel "pear.php.net" succeeded


まずPEARを更新して、

pear upgrade pear/Archive_Tar
pear/Archive_Tar requires PEAR Installer (version >= 1.5.4), installed version is 1.4.9
No valid packages found
upgrade failed


あれ、失敗?

pear install -f pear/Archive_Tar
warning: pear/Archive_Tar requires PEAR Installer (version >= 1.5.4), installed version is 1.4.9
downloading Archive_Tar-1.3.3.tgz ...
Starting to download Archive_Tar-1.3.3.tgz (18,119 bytes)
......done: 18,119 bytes
install ok: channel://pear.php.net/Archive_Tar-1.3.3


強引にねじ込みました。

PHPのparse_ini_fileでハマる

parse_ini_fileは設定ファイルをパースしてくれる関数ですが、使い勝手が良いのか悪いのか。
文字列に「”」ダブルクオートを含む場合、そのままでは記述できません。
エスケープしてみたり、文字列をほかのもので囲んでみたりしたけど、希望するとおりにならない。
しかたなくphpのリファレンスを見ると、定数で逃げればいいとの事。
iniを動的に作成したりする場合は、

define( 'QUOTE', '"' );

必須ということですね。

こういうやり方が残っているとは、PHPもまだまだだなぁと思う今日この頃でした。
ちなみに、検索で調べた中で文字列自体を定数にしろという意見もありましたが、定数と変数の違いを理解していない人が多いのは残念です。
とはいえ、自分も定数に逃げている部分もあるので、大きいことはいえませんが。

HTTP_REFERERは使うな

PHPで組んだものをIEデミルとどうも動きが怪しい。
ちょっと調べてみると、IEがセキュリティ設定の都合でHTTP_REFERERを吐いていないらしく、一部画面の推移に影響が出ていた。
てっきりJavaScriptだけだと思っていたのに、サーバにも情報を隠蔽していたらしい。
仕方がないので、HTTP_REFERERを使わないようにい変更するしかない・・・