路面管理のビッグデータを利用して路面状況を取得してみる

現在、「路面管理の高度化における実証実験」として、バス，タクシーなどの公共交通車両に取り付けられた安価なセンサーによって取得されたデータを収集し，それらを集約したビッグデータが公開されています。

 **路面管理ビッグデータのオープンデータコンテスト** 
http://micrms.force.com/apis

今回はこのデータを利用して地図上で路面の状況を確認できるようにしました。

 **路面状況Viewer** 
http://needtec.sakura.ne.jp/road_mgt/road_mgt.html

 **GitHub** 
https://github.com/mima3/road_mgt

![路面3.png](https://qiita-image-store.s3.amazonaws.com/0/47856/51ebcb1f-4dce-5fdd-839b-a4f2251a7846.png)


##路面管理の高度化における実証実験 APIについて
APIの仕様書は下記からダウンロードできます。
http://micrms.force.com/developer

データとしては大きく以下の３つに分けることができます。
・路面計測データ i-DRIMSという機械で計測したデータ。加速度、角速度、位置のデータ
・路面状況データ 路面計測データを分析した路面の状況のデータ
・路面緒元データ 自治体毎に短い区間で設定した道路管理情報

通常は路面状況データと路面緒元データを使用することになると思います。


###路面状況データの取得方法
路面状況を取得するには以下のURLに対してGETを行います。

http://roadmgt.herokuapp.com/api/v1/datapoints?data_type=road-surface

この結果は次のようなXMLとなります。

```xml
<rdf:RDF xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#" xmlns:geo="http://www.w3.org/2003/01/geo/wgs84_pos#" xmlns:rm="http://roadmgt.herokuapp.com/vocab/rm#" xmlns:rdfs="http://www.w3.org/2000/01/rdf-schema#">
  <rdf:Description rdf:about="http://roadmgt.herokuapp.com/api/v1/datapoints/road-surface_282">
    <rm:iri>2.65</rm:iri>
    <rm:step>0.0</rm:step>
    <rm:patching_num>0</rm:patching_num>
    <rm:municipality_id>1</rm:municipality_id>
    <rm:too_slow_fast_flag>0</rm:too_slow_fast_flag>
    <rm:subsidence_and_puddle>0.0</rm:subsidence_and_puddle>
    <rm:section_id>-1</rm:section_id>
    <rm:speed>37.55711977</rm:speed>
    <rdf:type>http://roadmgt.herokuapp.com/vocab/rm#road-surface</rdf:type>
    <geo:alt>0.0</geo:alt>
    <rm:rms>21.14314346</rm:rms>
    <geo:long>140.1434769</geo:long>
    <rm:rutting_amount>3.75</rm:rutting_amount>
    <rm:pothole_num>0</rm:pothole_num>
    <rm:speed_fluctuation_flag>0</rm:speed_fluctuation_flag>
    <rdfs:label>road-surface_282</rdfs:label>
    <rm:cracking_rate>5.3</rm:cracking_rate>
    <geo:lat>35.61753776</geo:lat>
    <rm:analysis_timestamp>2014-12-30 17:00:02.0</rm:analysis_timestamp>
    <rm:distance>220</rm:distance>
  </rdf:Description>
  <rdf:Description rdf:about="http://roadmgt.herokuapp.com/api/v1/datapoints/road-surface_294">
    <rm:speed_fluctuation_flag>1</rm:speed_fluctuation_flag>
    <rm:patching_num>0</rm:patching_num>
    <rm:iri>2.5</rm:iri>
    <rm:step>0.0</rm:step>
    <rm:too_slow_fast_flag>0</rm:too_slow_fast_flag>
    <rm:subsidence_and_puddle>0.0</rm:subsidence_and_puddle>
    <rm:section_id>-1</rm:section_id>
    <rm:municipality_id>1</rm:municipality_id>
    <rm:distance>340</rm:distance>
    <geo:alt>0.0</geo:alt>
    <rm:cracking_rate>7.5</rm:cracking_rate>
    <geo:long>140.1464737</geo:long>
    <rdf:type>http://roadmgt.herokuapp.com/vocab/rm#road-surface</rdf:type>
    <rm:rms>26.23188158</rm:rms>
    <geo:lat>35.61759593</geo:lat>
    <rm:pothole_num>0</rm:pothole_num>
    <rm:speed>42.15859739</rm:speed>
    <rm:analysis_timestamp>2014-12-30 17:00:02.0</rm:analysis_timestamp>
    <rm:rutting_amount>5.3</rm:rutting_amount>
    <rdfs:label>road-surface_294</rdfs:label>
  </rdf:Description>
  略
</rdf:RDF>
```

###路面状況を全データ取得する方法
APIを実行した時の最大取得件数はデフォルトで300件です。
以降のデータを取得するには、以下のようにoffsetを利用する必要があります。

(1)まず先頭を取得
http://roadmgt.herokuapp.com/api/v1/datapoints?data_type=road-surface&limit=300&offset=0

(2)正常にデータが取得できたら、offsetを変更して実行
http://roadmgt.herokuapp.com/api/v1/datapoints?data_type=road-surface&limit=300&offset=300

(3)これを以下のレスポンスになるまで繰り返す。

```
404 notFound
```

###路面状況のパラメータ
路面状況はパラメータを指定してデータをフィルタリングすることが可能です。
指定可能なパラメータは次の通りです。

|名称|型|説明|
|:--|:--|:--|
|municipality|xsd:int|自治体ID<BR>自治体のIDらしいが当然の権利のように説明が一切ないので意味がわかりません。<BR>1と11が入っていてます。おそらく、1が千葉市で11が豊中市だとは思います。|
|section|xsd:int|区間ID。おそらく、道路の区間を内部で一意のIDをふっているのでしょうがよくわかりません。-1とか入っています。|
|date_start|xsd:date|分析日。終了の指定とセットでない場合はエラー(YYYY-MM-DD)|
|date_end|xsd:date|分析日。開始の指定とセットでない場合はエラー(YYYY-MM-DD)|
|lat_start|xsd:double|緯度開始。終了の指定とセットでない場合はエラー|
|lat_end|xsd:double|緯度終了。開始の指定とセットでない場合はエラー|
|lon_start|xsd:double|経度開始。終了の指定とセットでない場合はエラー|
|lon_end|xsd:double|経度終了。開始の指定とセットでない場合はエラー|

経度緯度を指定した実行例：

http://roadmgt.herokuapp.com/api/v1/datapoints?data_type=road-surface&lat_start=34.66802946&lon_start=135.362425&lat_end=35.67436512&lon_end=140.2823427&offset=300&limit=300

 **注意**　
ここで紹介したパラメータ並びに、offset,limit以外のパラメータを指定するとエラーとなります。
このことは、以下のようなコードだとエラーになることを意味します。

```js
$.ajax({
  type : 'GET',
  url : 'http://roadmgt.herokuapp.com/api/v1/datapoints',
  cache: false // キャッシュを回避するとエラーになる。
  data : {
    data_type : 'road-surface',
    lat_start  : lat_start,
    lon_start : lon_start,
    lat_end  : lat_end,
    lon_end  : lon_end,
    offset : offset,
    limit : limit
  },
  success : function (data) {
  },
  'error' :  function(xhr, textStatus, error) {
  }
});
```
このことはIE系でデータが更新されていても、内容が変わらないというリスクを含むことになります。

cahce=falseの場合、パラメータにユニークな一時的な値を指定することで、別のリクエストとして認識させ、キャッシュの使用を回避しているのですが、これが利用できなくなります。

http://stackoverflow.com/questions/4303829/how-to-prevent-a-jquery-ajax-request-from-caching-in-internet-explorer

###ターゲットの直接指定
rdf:aboutに記述してある、URIを指定することで直接ターゲットを取得できます。

実行例：
http://roadmgt.herokuapp.com/api/v1/datapoints/road-surface_282

取得結果：

```xml
<rdf:RDF xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#" xmlns:geo="http://www.w3.org/2003/01/geo/wgs84_pos#" xmlns:rm="http://roadmgt.herokuapp.com/vocab/rm#" xmlns:rdfs="http://www.w3.org/2000/01/rdf-schema#">
<rdf:Description rdf:about="http://roadmgt.herokuapp.com/api/v1/datapoints/road-surface_282">
<rm:iri>2.65</rm:iri>
<rm:step>0.0</rm:step>
<rm:patching_num>0</rm:patching_num>
<rm:municipality_id>1</rm:municipality_id>
<rm:too_slow_fast_flag>0</rm:too_slow_fast_flag>
<rm:subsidence_and_puddle>0.0</rm:subsidence_and_puddle>
<rm:section_id>-1</rm:section_id>
<rm:speed>37.55711977</rm:speed>
<rdf:type>http://roadmgt.herokuapp.com/vocab/rm#road-surface</rdf:type>
<geo:alt>0.0</geo:alt>
<rm:rms>21.14314346</rm:rms>
<geo:long>140.1434769</geo:long>
<rm:rutting_amount>3.75</rm:rutting_amount>
<rm:pothole_num>0</rm:pothole_num>
<rm:speed_fluctuation_flag>0</rm:speed_fluctuation_flag>
<rdfs:label>road-surface_282</rdfs:label>
<rm:cracking_rate>5.3</rm:cracking_rate>
<geo:lat>35.61753776</geo:lat>
<rm:analysis_timestamp>2014-12-30 17:00:02.0</rm:analysis_timestamp>
<rm:distance>220</rm:distance>
</rdf:Description>
</rdf:RDF>
```

ターゲット名の後にプロパティ名を指定することで、プロパティーのみの取得ができます。
この際、「：」を「＿」に変更する必要があります。たとえば、「rm:step」を取得したい場合は「rm_step」と指定します。

実行例:
http://roadmgt.herokuapp.com/api/v1/datapoints/road-surface_282/rm_step

取得結果

```xml
<rdf:RDF xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#" xmlns:geo="http://www.w3.org/2003/01/geo/wgs84_pos#" xmlns:rm="http://roadmgt.herokuapp.com/vocab/rm#">
<rdf:Description rdf:about="http://roadmgt.herokuapp.com/api/v1/datapoints/road-surface_282">
<rm:step>0.0</rm:step>
</rdf:Description>
</rdf:RDF>
```

###レスポンスの説明
主なプロパティを以下に説明します。

|プロパティ|名称|型|説明|
|:--|:--|:--|:--|
|rm:analysis_timestamp|分析日時|xsd:dateTime|「2015-01-14 09:01:57」という形式で入っています。|
|rm:iri|IRI|xsd:double|International Roughness Index　舗装の平坦性（乗り心地）を客観的. に評価する尺度<BR>http://www.kandoken.jp/huroku/130913_2_kouenshiryo.pdf|
|rm:pothole_num|ポットホール数|xsd:int|道路の舗装表面が陥没してできた穴の数|
|rm:patching_num|パッチング数|xsd:int|舗装の損傷に対する応急処置を実施した数<BR>http://www.mlit.go.jp/road/ir/ir-council/pdf/roadstock05.pdf|
|rm:cracking_rate|ひび割れ率|xsd:double|http://www.town.oki.fukuoka.jp/file/gyousei/nyuusatsu/FILE_346_45.pdf|
|rm:rutting_amount|わだち掘れ深さ|xsd:double|http://www.fuzita.org/cod/rut_.html|
|rm:step|段差|xsd:double||
|rm:subsidence_and_puddle|沈下・水たまり|xsd:double||
|geo:lat|緯度|xsd:double||
|geo:long|経度|xsd:double||
|geo:alt|高度|xsd:double||

###路面緒元データの取得方法
基本的に路面状況と同じです。
以下のURLに対してGETを行います。

http://roadmgt.herokuapp.com/api/v1/datapoints?data_type=road-master&lat_start=34.66802946&lon_start=135.362425&lat_end=35.67436512&lon_end=140.2823427&offset=300&limit=300

現在は以下の赤線のデータしか存在していません。
![路面.png](https://qiita-image-store.s3.amazonaws.com/0/47856/a33881d6-a18b-2d99-5bef-cf68e0f5aae3.png)


拡大するとわかりますが、一応、車線毎にデータが存在するようです。

![路面2.png](https://qiita-image-store.s3.amazonaws.com/0/47856/2b2d886b-5ed7-70ac-9837-d104d649de13.png)

##プログラム言語で使用する

###Python
ここではPythonで路面状況を取得する方法を記述します。

```py:road_mgt.py
# -*- coding: utf-8 -*-
import urllib
import urllib2
from lxml import etree
import csv
from collections import defaultdict


def get_road_surface(offset, limit):
    """
    路面状況データ
    """
    url = ('http://roadmgt.herokuapp.com/api/v1/datapoints?data_type=road-surface&offset=%d&limit=%d' % (offset, limit))

    req = urllib2.Request(url)
    opener = urllib2.build_opener()
    conn = opener.open(req)
    cont = conn.read()
    parser = etree.XMLParser(recover=True)
    root = etree.fromstring(cont, parser)
    namespaces = {
        'geo' : 'http://www.w3.org/2003/01/geo/wgs84_pos#',
        'rdf' : 'http://www.w3.org/1999/02/22-rdf-syntax-ns#', 
        'rdfs' : 'http://www.w3.org/2000/01/rdf-schema#', 
        'rm' : 'http://roadmgt.herokuapp.com/vocab/rm#'
    }

    row = []
    datas = {}
    value_tags = root.xpath('//rdf:Description', namespaces=namespaces)
    value_tags = root.xpath('//rdf:Description', namespaces=namespaces)
    for value_tag in value_tags:
        label = value_tag.find('rdfs:label', namespaces).text
        step = value_tag.find('rm:step', namespaces).text
        alt = value_tag.find('geo:alt', namespaces).text
        analysis_timestamp = value_tag.find('rm:analysis_timestamp', namespaces).text
        rutting_amount = value_tag.find('rm:rutting_amount', namespaces).text
        municipality_id = value_tag.find('rm:municipality_id', namespaces).text
        speed_fluctuation_flag = value_tag.find('rm:speed_fluctuation_flag', namespaces).text
        section_id = value_tag.find('rm:section_id', namespaces).text
        distance = value_tag.find('rm:distance', namespaces).text
        long = value_tag.find('geo:long', namespaces).text
        iri = value_tag.find('rm:iri', namespaces).text
        cracking_rate = value_tag.find('rm:cracking_rate', namespaces).text
        pothole_num = value_tag.find('rm:pothole_num', namespaces).text
        subsidence_and_puddle = value_tag.find('rm:subsidence_and_puddle', namespaces).text
        speed = value_tag.find('rm:speed', namespaces).text
        rms = value_tag.find('rm:rms', namespaces).text
        lat = value_tag.find('geo:lat', namespaces).text
        too_slow_fast_flag = value_tag.find('rm:too_slow_fast_flag', namespaces).text
        patching_num = value_tag.find('rm:patching_num', namespaces).text

        row.append({
            'label' : label,
            'step' : step,
            'alt' : alt,
            'analysis_timestamp' : analysis_timestamp,
            'rutting_amount' : rutting_amount,
            'municipality_id' : municipality_id,
            'speed_fluctuation_flag' : speed_fluctuation_flag,
            'section_id' : section_id,
            'distance' : distance,
            'long' : long,
            'iri' : iri,
            'cracking_rate' : cracking_rate,
            'pothole_num' : pothole_num,
            'subsidence_and_puddle' : subsidence_and_puddle,
            'speed' : speed,
            'rms' : rms,
            'lat' : lat,
            'too_slow_fast_flag' : too_slow_fast_flag,
            'patching_num' :patching_num
        })
    return row

def get_meas_locale(offset, limit):
    """
    路面計測時の位置データ
    """
    url = ('http://roadmgt.herokuapp.com/api/v1/datapoints?data_type=meas-locale&offset=%d&limit=%d' % (offset, limit))

    req = urllib2.Request(url)
    opener = urllib2.build_opener()
    conn = opener.open(req)
    cont = conn.read()
    parser = etree.XMLParser(recover=True)
    root = etree.fromstring(cont, parser)

    namespaces = {
        'geo' : 'http://www.w3.org/2003/01/geo/wgs84_pos#',
        'rdf' : 'http://www.w3.org/1999/02/22-rdf-syntax-ns#', 
        'rdfs' : 'http://www.w3.org/2000/01/rdf-schema#', 
        'rm' : 'http://roadmgt.herokuapp.com/vocab/rm#'
    }

    row = []
    datas = {}
    value_tags = root.xpath('//rdf:Description', namespaces=namespaces)
    for value_tag in value_tags:
        label = value_tag.find('rdfs:label', namespaces).text
        gpstimestamp = value_tag.find('rm:gpstimestamp', namespaces).text
        course = value_tag.find('rm:course', namespaces).text
        measurement_start_timestamp = value_tag.find('rm:measurement_start_timestamp', namespaces).text
        mounting_direction = value_tag.find('rm:mounting_direction', namespaces).text
        car_model = value_tag.find('rm:car_model', namespaces).text
        long = value_tag.find('geo:long', namespaces).text
        lat = value_tag.find('geo:lat', namespaces).text
        alt = value_tag.find('geo:alt', namespaces).text
        model_number = value_tag.find('rm:model_number', namespaces).text
        car_number = value_tag.find('rm:car_number', namespaces).text
        speed = value_tag.find('rm:speed', namespaces).text
        vertical_accuracy = value_tag.find('rm:vertical_accuracy', namespaces).text
        measurement_timestamp = value_tag.find('rm:measurement_timestamp', namespaces).text
        horizontal_accuracy = value_tag.find('rm:horizontal_accuracy', namespaces).text
        row.append({
            'label' : label,
            'gpstimestamp' : gpstimestamp,
            'course' : course,
            'measurement_start_timestamp' : measurement_start_timestamp,
            'mounting_direction' : mounting_direction,
            'car_model' : car_model,
            'long' : long,
            'lat' : lat,
            'alt' : alt,
            'model_number' : model_number,
            'car_number' : car_number,
            'speed' : speed,
            'vertical_accuracy' : vertical_accuracy,
            'measurement_timestamp' : measurement_timestamp,
            'horizontal_accuracy' : horizontal_accuracy
        })
    return row

def get_road_surface_all():
    ret = []
    limit = 300
    offset = 0
    try:
        while True:
            print ('get_road_surface_all %d' % offset)
            r = get_road_surface(offset, limit)
            ret.extend(r)
            offset += limit
    except urllib2.HTTPError, ex:
        if ex.code == 404:
            return ret
        else:
            raise

```

get_road_surface_all()を使用すれば、全路面状況が取得できます。
プログラムとしてはurllibでAPIを実行して、レスポンスをlxmlで解析しているだけです。


###JavaScript
ここではJavaScriptで路面状況を取得する方法を記述します。

```js
  /**
   * 路面情報のAPIを実行
   */
  function getDataByRange(data_type, lat_start, lon_start, lat_end, lon_end, callback) {
    var offset = 0;
    var limit = 300;
    var obj = {};

    function loadRoadOffset(lat_start, lon_start, lat_end, lon_end, offset, limit, obj, cb) {

      console.log(offset, limit);
      $.ajax({
        type : 'GET',
        url : 'http://roadmgt.herokuapp.com/api/v1/datapoints',
        cache: true , //, cacheをfalseにするとエラーになる。おそらく変なパラメータを無視するのでなく、はじいている
        data : {
          data_type : data_type,
          lat_start  : lat_start,
          lon_start : lon_start,
          lat_end  : lat_end,
          lon_end  : lon_end,
          offset : offset,
          limit : limit
        },
        success : function (data) {
          console.log(data);
          var root = data.documentElement;
          var attrs = root.attributes;
          var records = root.childNodes;
          for(var i=0; i<records.length; i++){
            if(records[i].nodeName.match(/rdf:Description/i)){
              var s = records[i].attributes["rdf:about"].value;
              var props = records[i].childNodes;
              for(var j=0; j<props.length; j++){
                if(props[j].nodeType == 1){
                  var p = props[j].nodeName;
                  var o = props[j].textContent;
                  if (!obj[s]) {
                    obj[s] = {};
                  }
                  if (obj[s][p]) {
                    if (!Array.isArray(obj[s][p])) {
                      var tmp = arys[s][p];
                      obj[s][p] = [];
                      obj[s][p].push(tmp);
                    }
                    obj[s][p].push(o);
                  } else {
                    obj[s][p] = o;
                  }
                }
              }
            }
          }
          loadRoadOffset(lat_start, lon_start, lat_end, lon_end, offset + limit, limit, obj, cb);
        },
        'error' :  function(xhr, textStatus, error) {
          console.log(xhr);
          var err = xhr.responseText;
          if (err == '404 notFound') {
            cb(null , obj);
          } else {
            cb(err , null);
          }
        }
      });
    }
    loadRoadOffset(lat_start, lon_start, lat_end, lon_end, offset, limit, obj, function(err, res) {
      callback(err, res);
    });
  }
```

この関数は次のように実行します。

```js
getDataByRange('road-master', surfaceRange.lat_min, surfaceRange.long_min, surfaceRange.lat_max, surfaceRange.long_max, function(err, data) {
      console.log('loadRoad', err, data);
});
```

この処理は指定の範囲の道路緒元データを全て取得します。

##まとめ
「路面管理の高度化における実証実験」のAPIを実行することで路面の状況を取得することができます。

これにより道路の劣化状況などの可視化が行えます。