分布情報保持サーバ

ここでは、同綴地名の解決に「分布情報保持サーバ」を利用する方法を説明します。 この機能は GeoNLP Software Version 1.1.0 以降で利用可能です。

分布情報保持サーバとは?

GeoNLP では、地名解析辞書を適切に選択すれば「どの地名を抽出するか」をある程度制御することができます。しかし同名(同綴)地名が同じ辞書に存在する場合にどちらが選択されるか(曖昧地名解決)は、前後に出現する他の地名語との関係を用いたシステム内部の計算による「文脈スコア」で決まり、外部からは制御できません。

たとえば「豊洲はデルタ地帯に建設された工業地帯である。」という文に含まれる「豊洲」という地名語は、「日本の大字」辞書に江東区と八戸市の2つが登録されており、どちらが選択されるかは前後の文章に依存します。しかし対象文書が「青森県に関する文章」であることが分かっているならば、その外部知識を与えることで「八戸市の豊洲」を選択するように制御できます。その外部知識を提供するサーバが「分布情報保持サーバ」です。

この機能の概要を図に示します。

../../../_images/dist-server-overview.png

分布情報保持サーバは、候補となる地名語の経緯度を受け取り、「どちらの地名語を選択するべきか」という情報を「位置に対する重み」という形で返します。たとえば青森県内の地名ならば1.0、県外ならば0.0を返すサーバを利用すれば、必ず青森県内の地名が選択されます。

分布情報保持サーバへの問い合わせ

分布情報保持サーバを利用して地名解決を行うには、次のように parse オプション として dist-server を指定します。

parse オプションの例

{
  "method": "geonlp.parse",
  "params":
    [
      "豊洲はデルタ地帯に建設された工業地帯である。",
      {
        "geocoding":true,
        "threshold":0,
        "show-candidate":false,
        "dist-server":{
          "url":"http://www.info-proto.com/place/geonlp_dist.php",
          "method":"getAomoriWeight",
          "option":{"datetime":"201501011200"}
        }
      }
    ],
  "id": "1"
}

url

name:URL
type:string
default:必須
例:http://www.info-proto.com/place/geonlp_dist.php

分布情報保持サーバのエンドポイント URL を指定します。この URL が指す場所に独自の分布情報保持サーバを実装してください。サーバ側の実装については 分布情報保持サーバのサーバ側実装 を参照してください。

method

name:Method
type:string
default:必須
例:“getAomoriWeight”

分布情報保持サーバ側でどの処理を実行するか(どの分布情報を返すか)を選択するために指定するメソッド名です。 JSON-RPC の method として利用されるため、省略できません。

option

name:Option
type:任意
default:null
例:{“datetime”:”201501011200”}

分布情報保持サーバ側で実行される処理に、何らかのパラメータを渡したい場合に利用するオプションです。数値、文字列、配列、オブジェクトなど、JSON で記述できる値であれば何でも渡せます。

たとえば気温の時系列データを保持しているサーバに、時刻をオプションとして渡すといった使い方ができます。

分布情報保持サーバのサーバ側実装

分布情報保持サーバは JSON-RPC サーバとして実装します。 GeoNLP サーバからは次のフォーマットの JSON-RPC リクエストが送信されます。

method:dist-server オプションの “method”
params:第一パラメータに経緯度の配列、第二パラメータに dist-server オプションの “option” で指定された値
id:1から10000の間の整数値

JSON-RPC リクエストの例

{
  "id": 9384,
  "method": "getAomoriWeight",
  "params": [
    [
      [ "35.661795", "139.792314" ],
      [ "40.536695", "141.515195" ]
    ],
    {
      "datetime": "201501011200"
    }
  ]
}

このリクエストに含まれる経緯度の配列と同じ個数だけ、重みを数値の配列として result にセットして返します。 JSON-RPC の仕様に従い、エラーが発生しなかった場合には error に null をセットします。また id はリクエストの id と同じ値を返します。

parse の結果として返される地名語は、「GeoNLP 内部で計算したスコア」と「重み」の積が最大となるものが選ばれます。そのため重みとして 0 を返すとその地名語は選択されなくなります。1より大きい値を返しても問題ありませんが、オーバーフローを起こさないようあまり大きな値は返さないようにしてください。

JSON-RPC レスポンスの例

{
  "result": [ 1, 0 ],
  "error": null,
  "id": 9384
}

エラーが発生した場合には、やはり JSON-RPC の仕様に従い、result に null を、 error にエラーの理由(文字列)をセットして返します。

{
  "result": null,
  "error": "Service temporary not available",
  "id": 9384
}

PHP による分布情報保持サーバのサンプル

ダウンロード

PHP を利用した「分布情報保持サーバ」のサンプルコードを示します。 function execute() 内で method による分岐を行います。

getAomoriWeight の内部で、青森県のおおよその経緯度範囲の内外判定を行い、 0 または 1 をセットして返します。実際の分布情報保持サーバではこの部分で適切な計算を行ってください。

<?php

/**
 * File: geonlp_dist.php
 * 分布情報保持サーバの簡易実装サンプル
 */

/*
 * HTTP リクエストの処理
 */
function process_request() {
  $postdata = file_get_contents("php://input");
  $request_json = json_decode($postdata, true);
  $error = null;
  $id = null;
  $json_rpc_version = '2.0';

  if (is_null($request_json)) {
    $error = array('code'=>-32700, 'message'=>'Parse error', 'data'=>$postdata);
  } else {

    // RPC version チェック
    $json_rpc_version = '1.0';
    if (array_key_exists('jsonrpc', $request_json) && $request_json['jsonrpc'] === '2.0') {
      $json_rpc_version = '2.0';
    }

    // method と id をチェック
    if (!array_key_exists('method', $request_json) || !array_key_exists('id', $request_json)) {
      $error = array('code'=>-32600, 'message'=>'Invalid Request', 'data'=>$request_json);
    } else {
      $id = $request_json['id'];
      execute($request_json, $result, $error);
    }
  }

  // レスポンスを構築
  if (is_null($error)) {
    $response = array('result' => $result, 'error' => null, 'id' => $id);
    if ($json_rpc_version === '2.0') $response['jsonrpc'] = '2.0';
  } else {
    $response = array('result' => null, 'id' => $id);
    if ($json_rpc_version === '2.0') {
      $response['jsonrpc'] = '2.0';
      $response['error'] = $error;
    } else {
      $error_data = var_export($error['data'], true);
      $response['error'] = sprintf("[%d]:%s,%s", $error['code'], $error['message'], $error_data);
    }
  }

  // send response
  header('Content-Type: application/json-rpc');
  print json_encode($response);

  exit(0);
}

/*
 * JSON リクエストを処理する
 * 
 */ 
function execute($request_json, &$result, &$error) {
  $params = $request_json['params'];
  try {
    switch($request_json['method']) {
    case "getAomoriWeight": // 青森県内なら1,県外なら0
      $result = array();
      foreach ($params[0] as $i => $latlon) {
	list($lat, $lon) = $latlon; // $i 番目の地名語の経緯度
	if ($lat < 40.15 || $lat > 41.63
	    || $lon < 139.76 || $lon > 141.78) {
	  // 青森県のだいたいの範囲の外にある
	  $result[$i] = 0.0;
	} else {
	  // 青森県のだいたいの範囲の内にある
	  $result[$i] = 1.0;
	}
      }
      break;
    default: // 未定義のメソッド
      $result = null;
      $error = array('code'=>-32601, 'message'=>'Method not found', 'data'=>$request_json['method']);
    }
  } catch (Exception $e) {
    $result = null;
    $error = array('code'=>32000, 'message'=>'Server error', 'data'=>$e->getMessage());
  }
  return is_null($error) ? true : false;
}

process_request();