イントラネットで地図サービスを使いたい４‐ジオコーダー編‐

<a href = "https://qiita.com/notmushroomnot/items/67dd8f27d86880c51d1e">前の記事</a>

#検索機能について
　話をする前に、検索機能について軽く説明しておく。
　例えば、検索機能といえば、概ね名称を検索して座標等を割り出し地図上に表示させるものと、座標を検索して名称を割り出し地図上に表示させるものなどがある。
　これら、検索機能のうち、名称を検索して座標を取得させるものを**ジオコーダー**、座標から名称を割り出すものを**リバースジオコーダー**という。

#地理院地図のジオコーダー
　地理院地図のジオコーダーには<a href="http://newspat.csis.u-tokyo.ac.jp/geocode/">東大のジオコーダー</a>が使われている。
　実はこのジオコーダー、公開されているので使えるのだが、必要なものとして
　◾UNIX（Linuxを含む）が動作するサーバ
　◾GNU C++ 4.1.2 以降、および GNU Make などの開発環境
　◾PHP バインディングを利用したい場合は PHP 5.2.6 以降
　◾一般的なコンパイル、インストール手順に関する知識
　◾コンパイルエラーに負けない不屈の魂と、不具合を報告して頂ける優しい心
　が、あげられている。
　で、開発環境や一般的なコンパイル、インストール手順に関する知識について要件を満たしていなかったので、早々に導入をあきらめた。
　だが、どういうデータが返されるかは調べておく必要があるので、<a>https://msearch.gsi.go.jp/address-search/AddressSearch?q=東京都台東区浅草一丁目1番1号</a>を見てみると、AddressSearch.jsonというファイルが返ってくる。
　中身を見ると、geojson形式で以下のように記述されている。

```json:AddressSearch.json
[
  {
    "geometry": {
      "coordinates": [ 139.79747, 35.710876 ],
      "type": "Point"
    },
    "type": "Feature",
    "properties": {
      "addressCode": "",
      "title": "東京都台東区浅草一丁目１番１号"
    }
  }
]
```

#住所検索ジオコーダー
　さて、国土交通省の数値情報を使用していて、PHP5.3.3で動くかJAVASCRIPTで作成されたジオコーダーがないものかと探していたら、Githubで<a href="http://nlftp.mlit.go.jp/ksj/">国土数値情報</a>を使用してPHPで作られた<a href="https://github.com/demouth/DmGeocoder">ジオコーダーを発見<a/>した。リバースジオコーダーの機能も付いている。
　これをダウンロードして、地理院地図でも使えるように、example内にPHPファイルを作成し、以下のように記述する。
　ジオコーダーの仕様などに関しては<a href="https://github.com/demouth/DmGeocoder">Github</a>を見てほしい。

```php:adrs.php
<?php
ini_set('Display_errors', 'ON');
error_reporting(E_ALL);
mb_internal_encoding('UTF-8');
$LIB_DIR = realpath(dirname(__FILE__).'/../src/').'/';
require_once $LIB_DIR.'Dm/Geocoder.php';
require_once $LIB_DIR.'Dm/Geocoder/Address.php';
require_once $LIB_DIR.'Dm/Geocoder/Prefecture.php';
require_once $LIB_DIR.'Dm/Geocoder/Query.php';
require_once $LIB_DIR.'Dm/Geocoder/GISCSV.php';
require_once $LIB_DIR.'Dm/Geocoder/GISCSV/Finder.php';
require_once $LIB_DIR.'Dm/Geocoder/GISCSV/Reader.php';

//ジオコーディング
$address = Dm_Geocoder::geocode($_POST['q']);
//住所と緯度経度取り出し
ini_set('serialize_precision', 16);//不動小数点以下の精度を下げる
foreach($address as $value){
  //geojsonに変換
     $hako[] = array(
         "geometry" => $point =array(
                 "coordinates" => $lnglat = array($value->lng,$value->lat),
                 "type" => "Point",
          ),
         "type" => "feature",
         "properties" => $jusho = array(
                  "addressCode" => "",
                  "title" => $value->prefectureName.$value->municipalityName.$value->localName,
          ),
     );
}
?>
```

#その他のジオコーダの作成
　先ほどのジオコーダーは、住所検索をして返すものだったが、駅や空港も調べたい。と、いうことでこちらは自分でジオコーダーを自作する。
　駅や空港などで使用するジオコーダーは同じ形式で作成し、条件によって参照するデータベースを変更しているだけなので、代表して、空港の検索機能を紹介する。

```php:air.php
<?php
  ini_set('display_errors', 'ON');
  error_reporting(E_ALL);
  mb_internal_encoding('UTF-8');

  $Srh = $_POST['q'];
  $csv = file_get_contents(dirname(__FILE__).'/data/Air.csv');
  $csv = explode( "\n", $csv );
  $max = count( $csv );
  $cnt = 0;

  foreach($csv as &$value){
    if(strpos($value,$Srh) !== false){
       $podata[$cnt] = $value;
       $podata[$cnt] = explode(',', $podata[$cnt]);
       $cnt++;
    }
  }

  for($i=0;$i<$cnt;$i++){
    //geojson形式に変換
    $hako[] = array(
      "geometry" => $point =array(
         "coordinates" => $lnglat = array($podata[$i][3],$podata[$i][2]),
         "type" => "Point",
      ),
      "type" => "feature",
      "properties" => $jusho = array(
         "addressCode" => "",
         "title" => $podata[$i][0].'('.$podata[$i][1].')',
      ),
    );
  }
?>
```

```csv:data/air.csv
空港名,くうこうめい,緯度1,経度1
羽田空港,はねだくうこう,35.549898,139.787099
成田空港,なりたくうこう,35.765730,140.386271
```

　CSVファイルを読み込んで、$_POSTで受け取った検索ワードとstrposで照らし合わせてマッチしたものをgeojson形式で返す。
　「羽田空港」を「東京国際空港」や「はねだくうこう」、「HanedaAirport」などといった検索をした場合、CSVに登録されていなければ、表記のゆれに対応していないため、検索結果を返さない。このため、検索には正しい検索名を送る必要がある。
　なお、データには、<a href="http://nlftp.mlit.go.jp/ksj/">国土交通省の国土数値情報</a>を使用させていただいている。

#ジオコーダの出力と検索ワードの仕分け
　どのジオコーダに送るかの仕分けと受け取ったデータをJSONにエンコードするコードを紹介する。
　JSONエンコードする場合、地理院地図側にAddressSearch.jsonを返すようにPHPファイルの名前をAddressSearch.phpにしておく。
　php5.3.3を使用していて、JSONエンコードにJSON_UNESCAPED_UNICODE(※php5.4から使用可)が利用できないので、<a href="https://qiita.com/mpyw/items/819974cf6b48ef80fd0f">コチラ</a>を参考にphp5.3.3で対応している。
　php5.4以降は、function raw_json_encode以下を
　　header('Content-type: application/json; charset=utf-8');
　　echo json_encode($hako,JSON_UNESCAPED_UNICODE);
　とだけにしてほしい。

```php:AddressSearch.php
<?php
mb_internal_encoding('UTF-8');
$Podata = $_POST['q'];
$Prefacture = mb_substr($Podata,3,2);
$Pre = array('県','府','都','道');
//mb_convert_variables('UTF-8','SJIS-win',$Pre);
foreach($Pre as &$value){
  if(mb_substr($Podata,-2) == "空港"){
     $DirPath = realpath(dirname(__FILE__).'/AirGeocoder/Air.php');
     require_once $DirPath;
     break;
  }elseif(mb_substr($Podata,-1) == "港"){
     $DirPath = realpath(dirname(__FILE__).'/PortGeocoder/Port.php');
     require_once $DirPath;
     break;
  }elseif(mb_substr($Podata,-1) == "駅"){
     $DirPath = realpath(dirname(__FILE__).'/StGeocoder/st.php');
     require_once $DirPath;
     break;
  }elseif(mb_substr($Podata,-2) == "原発" || mb_substr($Podata,-3) == "発電所"){
     $DirPath = realpath(dirname(__FILE__).'/PPGeocoder/PP.php');
     require_once $DirPath;
     break;
  }elseif(strpos($Prefacture,$value) !== false){
     $DirPath = realpath(dirname(__FILE__).'/DmGeocoder/example/adrs.php');
     require_once $DirPath;
     break;
  }else{
     $DirPath = realpath(dirname(__FILE__).'/DmGeocoder/example/adrs.php');
     require_once $DirPath;
     break;
  }
}
function raw_json_encode($input, $flags = 0) {
    $fails = implode('|', array_filter(array(
        '\\\\',
        $flags & JSON_HEX_TAG ? 'u003[CE]' : '',
        $flags & JSON_HEX_AMP ? 'u0026' : '',
        $flags & JSON_HEX_APOS ? 'u0027' : '',
        $flags & JSON_HEX_QUOT ? 'u0022' : '',
    )));
    $pattern = "/\\\\(?:(?:$fails)(*SKIP)(*FAIL)|u([0-9a-fA-F]{4}))/";
    $callback = function ($m) {
        return html_entity_decode("&#x$m[1];", ENT_QUOTES, 'UTF-8');
    };
    return preg_replace_callback($pattern, $callback, json_encode($input, $flags));
}
header('Content-type: application/json; charset=utf-8');
echo raw_json_encode($hako);
?>
```

　社内で利用するため、以上のような簡易なものとなっている。
　必要に応じて、検索ワードの追加、削除を行うことで、精度は増せると、思う……たぶん…………（゜゜）
　現状はこれで十分なのでよしとする。
　**戒めだが、一時BOMで積んだのでファイルを保存する場合は、BOMなしで保存しよう。**
　なお、ディレクトリは以下のようになっている。

```
　Directory
     ├──AddressSearch.php
     ├──AirGeocoder
     │  ├──Air.php
     │  └──data
     │    └─Air.csv 
     ├──DmGeocoder
     │  ├──example
     │  │　└─adrs.php
～～～～～～～～～～～～～～～～～
以下略
```    

#地理院地図側の設定
　さて、ジオコーダーを作成しても地理院地図側を設定しなければ、動かない。
　地理院地図側のジオコーダー関連は、gsimaps.jsの501行目から516行目と17123行目から17141行目にかけて記述がある。

####501行目から516行目
CONFIG.SERVERAPI.CHIMEI_SEARCHのパスを設置したAddressSearch.phpのパスに変更する。

```javascript:gsimaps.js_Line501-516
// サーバーサイドAPI
CONFIG.SERVERAPI = {};

CONFIG.SERVERAPI.GETADDR = "https://mreversegeocoder.gsi.go.jp/reverse-geocoder/LonLatToAddress";
CONFIG.SERVERAPI.CHIMEI_SEARCH = "https://msearch.gsi.go.jp/address-search/AddressSearch";
CONFIG.SERVERAPI.CHIMEI_SEARCH_ADDITIONALPARAMS = {
  "lang": "ja,en",
  "zl": "T",
  "ilvl": "T"
};

//for IE9
if ((ua.indexOf("msie") >= 0) && (vs.indexOf("msie 9") >= 0)) {
  CONFIG.SERVERAPI.GETADDR = "http://mreversegeocoder.gsi.go.jp/reverse-geocoder/LonLatToAddress";
  CONFIG.SERVERAPI.CHIMEI_SEARCH = "http://msearch.gsi.go.jp/address-search/AddressSearch";
}
```


```javascript:gsimaps.js_Line501-516
// サーバーサイドAPI
CONFIG.SERVERAPI = {};

CONFIG.SERVERAPI.GETADDR = "https://mreversegeocoder.gsi.go.jp/reverse-geocoder/LonLatToAddress";
CONFIG.SERVERAPI.CHIMEI_SEARCH = "AddressSearch.phpのパス";
CONFIG.SERVERAPI.CHIMEI_SEARCH_ADDITIONALPARAMS = {
  "lang": "ja,en",
  "zl": "T",
  "ilvl": "T"
};

//for IE9
if ((ua.indexOf("msie") >= 0) && (vs.indexOf("msie 9") >= 0)) {
  CONFIG.SERVERAPI.GETADDR = "http://mreversegeocoder.gsi.go.jp/reverse-geocoder/LonLatToAddress";
  CONFIG.SERVERAPI.CHIMEI_SEARCH = "AddressSearch.phpのパス";
}
```

####17123行目から17141行目
 ajaxのtypeをPOSTに変更する。

```javascript:gsimaps.js_Line17123-17141

  searchChimei: function (q, pref, muni) {
    var constraint = '';
    var url = CONFIG.SERVERAPI.CHIMEI_SEARCH;
    var parameter = { "q": q };

    if (CONFIG.SERVERAPI.CHIMEI_SEARCH_ADDITIONALPARAMS) {
      for (var key in CONFIG.SERVERAPI.CHIMEI_SEARCH_ADDITIONALPARAMS) {
        parameter[key] = CONFIG.SERVERAPI.CHIMEI_SEARCH_ADDITIONALPARAMS[key];
      }
    }
    return $.ajax({
      type: "GET",
      url: url,
      data: parameter,
      dataType: "json",
      timeout: 30000,
      success: L.bind(this.setChimeiRusult, this),
      error: function () {
      }
    });
  },
```
```javascript:gsimaps.js_Line17123-17141

  searchChimei: function (q, pref, muni) {
    var constraint = '';
    var url = CONFIG.SERVERAPI.CHIMEI_SEARCH;
    var parameter = { "q": q };

    if (CONFIG.SERVERAPI.CHIMEI_SEARCH_ADDITIONALPARAMS) {
      for (var key in CONFIG.SERVERAPI.CHIMEI_SEARCH_ADDITIONALPARAMS) {
        parameter[key] = CONFIG.SERVERAPI.CHIMEI_SEARCH_ADDITIONALPARAMS[key];
      }
    }
    return $.ajax({
      type: "POST",
      url: url,
      data: parameter,
      dataType: "json",
      timeout: 30000,
      success: L.bind(this.setChimeiRusult, this),
      error: function () {
      }
    });
  },
```

 一応、覚書だが、地理院地図は、geojson形式で受け取ったデータでも、POINTタイプしか処理できない。

#リバースジオコーダーに関して
　東大ジオコーダにデータを送った場合、LonLatToAddress.jsonというファイルが返ってくる。
　中身については、下記の通り
　http://mreversegeocoder.gsi.go.jp/reverse-geocoder/LonLatToAddress?lon=139.229393&lat=35.521259

```　
{
　"results":{
　　"muniCd":"14402",
　　"lv01Nm":"宮ヶ瀬"
　}
}
```

　一応、Dmgeocoderを利用して、リバースジオコーダを作成した。
~~　が、どうも処理を行ってくれないので、現在保留している。~~
　一応処理は行ってくれているようだったので<a href="https://qiita.com/notmushroom/items/6a19c6db5392e5249ef4">別の記事</a>で紹介する