OSM(Open Street Map)の文字コードは UTF-8 である。OSMデータの地名(丁目)では、 ○○五丁目、○○5丁目、○○5、○○5 などマッパーにより様々な表記が使われている。
比較的に多いのは○○五丁目であるが、マイOSM地図としては、なるべくスリムにしたい。 交差点名は概ね「○○五丁目」表記のため、これと区別するためにも、 地名は「○○5」表記としたい。番号はまずは半角としよう。
本ページではこのような地名(丁目)変換プログラムを考える。
記事[1]によると、UTF-8コードは以下のようである。文字は 1〜3 バイトで表わされる。 先頭バイトが 0x7f 以下ならば1バイト、0x80〜0xdf ならば2バイト、0xe0〜0xef ならば3バイトとなる。
この表には 0xf0〜0xff で始まる4〜6バイト文字が省略されているようだ。
Unicodeのコードポイント | UTF-8 |
0x0000〜0x007f (ASCII) | 0x00〜0x7f |
0x0080〜0x07ff (各国アルファベット) | 0xc080〜0xdfbf |
0x0800〜0xffff (インド系諸文字、句読点、学術記号、絵文字、東アジアの諸字、全角、半角形) | 0xe08080〜0xefbfbf |
記事[2]では、4〜6バイト文字もある。
コード | カテゴリ | 備考 |
---|---|---|
00-7x | 1バイト文字 | US-ASCIIにおなじ |
8x,9x,Ax,Bx | 多バイト文字の2バイト目以降 | |
Cx,Dx | 2バイト文字の開始バイト | |
Ex | 3バイト文字の開始バイト | 漢字はおおむねこれで開始 |
Fx | 4バイト以上の文字の開始バイト | F0-F7は4バイト、F8-FBは5バイト、FC-FDは6バイト |
多分、一、二、... 、十 および 0、1、... 、9 は3バイト文字であろう。
○○丁目のように、末尾が丁目の場合にのみ変換する。すなわち、十三(じょうそう)を 13 とはしない。
地名(placeタグ)のみ変換し、交差点名(highway=traffic_signal)、店名などは変換しない。
変換したくないケースがあれば例外処理とする。
途中段階の次のプログラムで2021年3月21日時点の japan.osm.pbf を調べた。 「○○△丁目」で、△の 1, 2, ... を除外すると、○○は全て3バイト文字であることが確認できた。 例外は「=鎌倉台一丁目」であるが、これは明らかに入力ミスであるから、OSMデータを訂正した。
したがって、地名(丁目)変換プログラムでは、丁目が元々半角数字であるケースと 3バイト文字だけを考慮すればよい。その他の文字が含まれれば、 警告メッセージを出して無変換とすればよい。
// ○○十五丁目 → ○○15 string TagConvert::chome(string str) { const char * Nums1[] = { "一", "二", "三", "四", "五", "六", "七", "八", "九", "十" }; const char * Nums2[] = { "1", "2", "3", "4", "5", "6", "7", "8", "9", "0" }; unsigned char *top = (unsigned char *)str.c_str(); const char *bgn = NULL; // 番号の始まり unsigned char *end = top + str.size() - 6; // 6 は "丁目" の長さ int num = 0; for (unsigned char* p = (unsigned char*)str.c_str(); p < end; ) { int c = (int)*p; if (c <= 0x7f) { // 1バイト文字 if ('0' <= c && c <= '9') { num = num * 10 + (c - '0'); } else if (c != ' ') { cerr << "丁目変換エラー: " << str << "\n"; return str; // 無変換 } p++; } else if (0xc0 <= c && c <= 0xdf) { // 2バイト文字 cerr << "2バイト文字: " << str << "\n"; p += 2; } else if (0xe0 <= c && c <= 0xef) { // 3バイト文字 p += 3; } else if (0xf0 <= c && c <= 0xf7) { // 4バイト文字 cerr << "4バイト文字: " << str << "\n"; p += 4; } else if (0xf8 <= c && c <= 0xfb) { // 5バイト文字 cerr << "5バイト文字: " << str << "\n"; p += 5; } else if (0xfc <= c && c <= 0xfd) { // 6バイト文字 cerr << "6バイト文字: " << str << "\n"; p += 6; } else { cerr << "丁目変換エラー: " << str << "\n"; return str; // 無変換 } } return str; }
地名自体に数字を含むケースがあるため、番号の読み取りは末尾から行う。 以下のプログラムによりざっとチェックした限りでは所望の結果が得られた。
// ○○十五丁目 → ○○15 string TagConvert::chome(string str) { const char * Nums1[] = { "十", "一", "二", "三", "四", "五", "六", "七", "八", "九" }; const char * Nums2[] = { "0", "1", "2", "3", "4", "5", "6", "7", "8", "9" }; unsigned char *top = (unsigned char *)str.c_str(); unsigned char *end = top + str.size() - 6; // 6 は "丁目" のバイト長 unsigned char *p = end; int num = 0; int n; int fact = 1; bool ten = false; while (p > top) { int c = (int)p[-1]; if (c <= 0x7f) { // 1バイト文字 p--; if ('0' <= c && c <= '9') { num += (c - '0') * fact; fact *= 10; } else if (c != ' ') { cerr << "丁目変換エラー: " << str << "\n"; return str; // 無変換 } } else if (0xe0 <= p[-3] && p[-3] <= 0xef) { // 3バイト文字 p -= 3; for (n = 0; n < 10; n++) { if (strncmp((const char*)p,Nums1[n],3) == 0) break; } if (n == 10) { for (n = 0; n < 10; n++) { if (strncmp((const char*)p,Nums2[n],3) == 0) break; } } if (n == 10) { p += 3; break; } if (n == 0) { ten = true; n = 10; if (num >= 10) num -= 10; if (fact >= 10) fact /= 10; } else if (num > 0 && !ten) { p += 3; break; } num += n * fact; fact *= 10; } else { cerr << "丁目無変換: " << str << "\n"; return str; // 無変換 } } string out((const char*)top, p-top); out = out + to_string(num); return out; }