2017年5月17日水曜日

XMLファイル取り込み:地図情報

国土数値情報(http://nlftp.mlit.go.jp/ksj/index.html)の政策区域を取り込んでみる。例として医療圏を利用する。
内容を確認するため、静岡県をダウンロードして内容をチェックする。

1.構造の確認
ファイルの下の方にあるブロックから見ていく
これを見ると
FirstMedicalArea : 一次医療圏
administrativeAreaCode : 22101 地域コード(5桁)
cityName : 葵区
secondMedicalAreaCode : 2205 二次医療圏コード(4桁)
secondMedicalAreaName : 静岡  二次医療圏名
settingFlag : 2 一次医療圏を医療計画等で定義しているかどうかを判断するためのコードなのでいらない?(ほとんど2?)

これに対して「sf1」で検索すると「cv1_0」が範囲の情報に当たる。上にある緯度、経度のセットが境界情報
続いて二次医療圏のブロックを見る
SecondMedicalArea
これを見ると
AdministrativeAreaCode : 22207, 22210
CityName :  富士宮市、富士市
secondMedicalAreaCode : 2204
secondMedicalAreaName : 富士
areaOfRegionalMedicalPlan : 都道府県が定めた地域保健医療計画に掲載されている面積(㎡) : 634.01
areaOfGsi : 面積(国土地理院): 634.01
populationOfRegionalMedicalPlan : 人口(医療計画): 385752
totalPopulationOfBrr : 総人口(住民基本台帳): 394831
under15YearsOldPopulationOfBrr : 人口(15才未満)(住民基本台帳)55125
between15And64YearsOldPopulationOfBrr : 248263
over65YearsOldPopulationOfBrr : 91443

この「sf1114」で検索すると、領域情報がわかる
ThirdMedicalAreaに関しては、都道府県のはずなのでなぜ分かれているかわからないけど、あるので作っておく。
この「sf2192」で検索すると境界情報?があった
テーブル構成は次のようになる
都道府県テーブル(都道府県id, 名前)
2次医療圏テーブル(2次医療圏id(4桁),名前, 地域コード(5桁), 面積, 人口, 15歳未満人口, 15-64歳人口, 65歳以上人口)
2次医療圏境界テーブル(id, 2次医療圏id, latitude, longitude)
地域テーブル(地域コード(5桁), 名前)

テーブルを作成しbakeしておきます。
zonesテーブル(2次医療圏)にuploadアクションを追加します。この辺は前回と同じ。
続いてimportアクションの作成。前回より難しいのは2次医療圏のテーブルに情報を入れると同時に、境界情報をboundariesテーブルに入れないといけない。2次医療圏の情報挿入ループの中に、boundariesテーブルの挿入ループを書いていく。
public function import(){
APP::import('Model','Boundary');
$this->Boundary = new Boundary;
if($this->request->is('post')){
$up_file = $this->data['Zone']['XmlFile']['tmp_name'];
$fileName = 'post.xml';
if(is_uploaded_file($up_file)){
move_uploaded_file($up_file, $fileName);
$xmlArray = Xml::toArray(Xml::build($fileName));
foreach($xmlArray['Dataset']['ksj:SecondMedicalArea'] as $line){//2次医療圏の情報を入れる
$data = array(
'id' => $line['ksj:secondMedicalAreaCode'],
'population' => $line['ksj:totalPopulationOfBrr'],
'area' => $line['ksj:areaOfGsi'],
'population15' => $line['ksj:under15YearsOldPopulationOfBrr'],
'population1564' => $line['ksj:between15And64YearsOldPopulationOfBrr'],
'population65' => $line['ksj:over65YearsOldPopulationOfBrr']
);
$this->Zone->save($data);
$region_id = str_pad($line['ksj:secondMedicalAreaCode'],4,'0',STR_PAD_LEFT);//2次医療圏idは4桁
for($i = 0; $i < count($xmlArray['Dataset']['gml:Curve']);$i++){
$id = substr($xmlArray['Dataset']['gml:Curve'][$i]['@gml:id'],2);//cvXXXX_0なので最初のcvをとる
$id2 = explode("_",$id);//後ろの_0をとる
$link = substr($line['ksj:area']['@xlink:href'],3);//#sfXXXXで3文字目から
if($id2[0] == $link){
$latlng = explode(" ",$xmlArray['Dataset']['gml:Curve'][$i]['gml:segments']['gml:LineStringSegment']['gml:posList']);
for($j = 0; $j < count($latlng);$j+=7){
$data = array(
'region_id' => $region_id,
'latitude' => $latlng[$j],
'longitude' => $latlng[$j+1]
);
$this->Boundary->create($data);
$this->Boundary->save();
}
}
}
}
}
}
$this->redirect(array('action'=>'index'));
}
view raw import hosted with ❤ by GitHub

これでファイルをアップロードすると、無事に境界情報が入る(アップロードに10分位かかる)
あとは同じやり方で1次医療圏(地域)をやる。
 1次医療圏境界テーブル(id, 地域id(5桁), 緯度, 経度)

2017年5月4日木曜日

XMLファイルの取り組み

国土交通省(http://nlftp.mlit.go.jp/ksj/index.html)から出ているデータが良いものが多かったので、取り込めるようにプログラムをしました。ただしGISデータでXMLなのでちょっと工夫が必要でした。環境はcakephp+MySQLです。
利用データ例は学校データを用いました。

(1)XMLの分析
http://nlftp.mlit.go.jp/ksj/gml/datalist/KsjTmplt-P29.html
から静岡版をダウンロードして中身を確認しました。
上から「n1」はファイル上部にある場所情報(緯度、経度)とリンクします。
次の「22101」は行政コード5桁(http://nlftp.mlit.go.jp/ksj/gml/codelist/AdminAreaCd.html) -> regionsテーブルとして作成
次の「16」は公共施設大分類コード (http://nlftp.mlit.go.jp/ksj/gml/codelist/PubFacMaclassCd.html)ですが、 必要ないので今回は無視
次の「16001」は公共施設小分類コード(http://nlftp.mlit.go.jp/ksj/gml/codelist/PubFacMinclassCd.html) ですがこれも無視
次の「16001」は学校分類コード(http://nlftp.mlit.go.jp/ksj/gml/codelist/SchoolClassCd.html)でこれは必要 -> categoriesテーブルとして作成
学校名、住所、最後の「3」は管理者コード(http://nlftp.mlit.go.jp/ksj/gml/codelist/AdminCd.html) は無視
これでmainのschoolsテーブルは
(id,name,address,region_id,category_id,latitude,longitude)として作成

(2)XMLの取り込み
上記の3個のテーブルをbakeしておきます。
$ cp View/Schools/add.ctp View/Schools/upload.ctp
upload.ctpを作成し、ファイルをアップロードするように編集します。
<div class="schools form">
<?php echo $this->Form->create('School',array('url'=>'import','type'=>'file')); ?>
<fieldset>
<legend><?php echo __('Add School'); ?></legend>
<?php
echo $this->Form->input('XmlFile',array('label'=>'','type'=>'file'));
?>
</fieldset>
<?php echo $this->Form->end(__('Submit')); ?>
</div>
<div class="actions">
<h3><?php echo __('Actions'); ?></h3>
<ul>
<li><?php echo $this->Html->link(__('List Schools'), array('action' => 'index')); ?></li>
<li><?php echo $this->Html->link(__('List Regions'), array('controller' => 'regions', 'action' => 'index')); ?> </li>
<li><?php echo $this->Html->link(__('New Region'), array('controller' => 'regions', 'action' => 'add')); ?> </li>
<li><?php echo $this->Html->link(__('List Categories'), array('controller' => 'categories', 'action' => 'index')); ?> </li>
<li><?php echo $this->Html->link(__('New Category'), array('controller' => 'categories', 'action' => 'add')); ?> </li>
</ul>
</div>
view raw upload.ctp hosted with ❤ by GitHub

uploadアクションも作成します(空です)。
$ vi Controller/SchoolsController.php
public function upload() {}

次にimportアクションの作成をします。XMLの中身を確認しながら次のようにします。
public function import(){
if($this->request->is('post')){
$up_file = $this->data['School']['XmlFile']['tmp_name'];
$fileName = 'post.xml';
if(is_uploaded_file($up_file)){
move_uploaded_file($up_file, $fileName);
$xmlArray = Xml::toArray(Xml::build($fileName));
$i = 0;
foreach($xmlArray['Dataset']['ksj:School'] as $line){
$latlng = explode(" ",$xmlArray['Dataset']['gml:Point'][$i++]['gml:pos']);
$data = array(
'name' => $line['ksj:name'],
'address' => $line['ksj:address'],
'region_id' => str_pad($line['ksj:administrativeArea']['@'],5,'0',STR_PAD_LEFT),
'category_id' => $line['ksj:type'],
'latitude' => $latlng[0],
'longitude' => $latlng[1]
);
$this->School->create($data);
$this->School->save();
}
}
}
$this->redirect(array('action'=>'index'));
}
view raw import hosted with ❤ by GitHub


これでファイルをアップロードしてみると下記のようになります。

追記
利用するコントローラの上部に
App::uses('Xml', 'Utility');
が必要でした。

2017年5月3日水曜日

災害時病院対応シミュレーション

災害時における病院のキャパを考えてみる。

1.情報の表示
[利用データベース]
消防署テーブル(id, 消防署名, 地域id(5桁), 緯度, 経度, 種別, 住所)
地域テーブル(id(5桁), 地域名, 人口, その他の情報(人口比率など), 県コード, 2次医療圏id)
病院テーブル(病院ID, 病院名, 地域id(5桁), 住所, 緯度, 経度, 医師数, 病床数, 病院区分)
2次医療圏テーブル(id(4桁), 2次医療圏名, その他情報) 
県テーブル (id, name)

ここで2次医療圏をベースにして考える。
2次医療圏を指定 -> 対象地域が表示(人口、高齢化率など) -> 地域の病院情報(医師数、病床数)も得られる

2.待ち行列でモデル化
できるだけシンプルにモデル化する
(i) M/M/sでモデル化:2次医療圏を対象
[客の発生]
二次医療圏全人口PのX%が患者として、P*X(人)/日が患者発生数とする。
Xを定数:20%、または高齢化率で算出(15歳未満、65歳以上の比率がいいかも)
1時間当たりの患者発生数 = P*X(人/日)/24
例えばある2次医療圏の総人口が402525人、65歳以上率が30.83%とすると
1時間当たりの患者発生数は、402525 * 0.3083 /24 = 5170.7690 (人)となる。

[サーバー数]
病院数とする。ある2次医療圏では病院数が40。

[サービス時間]
医者一人いると患者をS時間で処置可能とする。
1病院当たりの医者の数をNとすると、1時間当たりに処置可能な患者数は(1/S)*N(人)
診療時間は10分未満が67%ということなので、5分(1/12時間)とする。
(http://www.mhlw.go.jp/toukei/saikin/hw/jyuryo/05/kekka2.html
二次医療圏での総医者数をNとして、サービス率は (1/S)*N/病院数とする。
例えばある2次医療圏では病院数が40、医者数が570人なので、1病院当たりの医者数は570/40=14.25人となる。サービス率は 12 * 14.25 = 171となる。
サービス率はどの病院でも同じとする。

[計算]
到着率 : 5170.7690、サービス率 : 171、サーバ数 : 40として計算すると
利用率:0.75596038011696
平均待ち人数:0.18931836530515 (人/時間)
平均系内人数:30.427733569984 (人/時間)
平均待ち時間:3.6613193377068E-5 (時間)
平均系内時間:0.0058845664097513 (時間)

実際に静岡県西部でやるとこんな感じ。

コード
[mmsクラス]
<?php
class MyMMS{
private $lambda, $mu, $row, $Q, $L, $W, $U, $s;
public function __construct($lambda, $mu, $s){
$this->lambda = $lambda;
$this->mu = $mu;
$this->s = $s;
}
public function factorial($n){
$fact = 1;
if($n == 0)
return $fact;
else{
for($i = $n; $i>0; $i--)
$fact *= $i;
return $fact;
}
}
public function functionP0($a, $row){
$temp = 0;
for($k = 0; $k <= $this->s - 1; $k++){
$temp += (pow($a, (double)$k) / $this->factorial($k));
}
$temp +=( pow($a,(double) $this->s)/ ($this->factorial($this->s)*(1.0 - $row)));
$p0 = pow($temp,-1);
return $p0;
}
public function functionMMs(){
//$p0, $a;
$a = $this->lambda / $this->mu;
$this->row = $a/(double) $this->s;
$p0 = $this->functionP0($a, $this->row);
$this->Q = (pow((double) $this->s,(double) $this->s) * pow($this->row,(double) $this->s + 1.0)) / ($this->factorial($this->s) * pow(1.0 - $this->row,2.0)) * $p0;//待ち人数
$this->L = $this->Q + $a;//系内人数
$this->W = $this->Q / $this->lambda;//待ち時間
$this->U = $this->W + (1/$this->mu);//系内時間
$this->UU = $this->L / $this->lambda;//系内時間
}
public function getrow() {
return $this->row;
}
public function getQ() {
return $this->Q;
}
public function getL() {
return $this->L;
}
public function getW() {
return $this->W;
}
public function getU() {
return $this->U;
}
public function getResult(){
$this->functionMMs();
$result = array(
'lambda' => $this->lambda,
'mu' => $this->mu,
's' => $this->s,
'row' => $this->row,
'Q' => $this->Q,
'L' => $this->L,
'W' => $this->W,
'U' => $this->U,
'UU' => $this->UU
);
return $result;
}
}
?>
view raw MyMMS hosted with ❤ by GitHub


[mmsアクション]
public function mms($id = null) {
if (!$this->Zone->exists($id)) {
throw new NotFoundException(__('Invalid zone'));
}
$options = array('conditions' => array('Zone.' . $this->Zone->primaryKey => $id),'recursive' => 2);
$this->set('zone', $this->Zone->find('first', $options));
$zone = $this->Zone->find('first', $options);
$doctor = 0;
$hospital_num = 0;
$population = 0;
$rate_65 = 0;
foreach($zone['Region'] as $region){
$population += $region['allpopulation'];
$rate_65 += $region['rate_population65'];
foreach($region['Hospital'] as $hospital){
$doctor += $hospital['doctor'];
$hospital_num ++;
}
}
$rate_65 = $rate_65 / count($zone['Region']);
$lambda = $population * $rate_65 /100 /24;
$mu = 12 * $doctor / $hospital_num;
App::import('Vendor', 'MyMMS');
$mymms = new MyMMS($lambda, $mu, $hospital_num);
$mms = $mymms->getResult();
print_r($mymms->getResult());
$data = array(
'id' => $zone['Zone']['id'],
'lambda' => $mms['lambda'],
'mu' => $mms['mu'],
'server' => $mms['s'],
'rho' => $mms['row'],
'q' => $mms['Q'],
'l' => $mms['L'],
'w' => $mms['W'],
'u' => $mms['U'],
);
$this->Zone->save($data);
}
view raw mms action hosted with ❤ by GitHub


 (ii)優先権付き待ち行列でモデル化(トリアージをするので)
災害時に病院に到着する客に優先度をつける。
参考
https://ipsj.ixsq.nii.ac.jp/ej/?action=repository_action_common_download&item_id=7376&item_no=1&attribute_id=1&file_no=1
http://www.orsj.or.jp/~archive/pdf/bul/Vol.41_02_100.pdf
[客の発生]
全人口PのX%が患者として、P*X(人)/日が患者発生数とする。PX*高齢者率Y=優先患者数(人/日)とする。その他を非優先患者数とする。
優先患者数/24 = 優先患者発生数/時となり、これをλ1とする。その他をλ2とする。


2017年5月2日火曜日

消防署データの構造確認

全国の消防署データが欲しく、国土交通省のデータを探して中身を確認しました。
データはここから探しました。
http://nlftp.mlit.go.jp/ksj/gml/datalist/KsjTmplt-P17.html
ダウンロードして展開し、今回は静岡県のデータを使いました。
P17-12_22.xml を開きます。

(1)消防署情報
これを見ると上から「fs_1」とあります。これはIDですが、検索しても他に出てこないので無視します。次は消防署、次に「pt_fsn1」とあります。これで検索すると
となり、この消防署の緯度、経度となります。
次に「22101」ですが「都道府県コードと市区町村コードからなる、行政区を特定するためのコード。」とあります。22+101と考えることになります。これについては以前研究で使ったデータを再利用したいと思います。
これで例えば「22101」で検索すると、他の静岡市の消防署が出てきます。 次のccdの「1」ですが、「消防本部=1、消防署=2、分署・出張所=3」となります。これで行政コードが同じ中に、コードによって消防本部から消防署、分署の階層構造が出来ます。
消防署の境界もありますが、今回はこのデータをデータベース化します。病院テーブルは既存のものです。

消防署テーブル(id, 消防署名, 地域id(5桁), 緯度, 経度, 種別, 住所)
地域テーブル(id(5桁), 地域名, 人口, その他の情報(人口比率など), 県コード)
病院テーブル(病院ID, 病院名, 地域id(5桁), 住所, 緯度, 経度, 医師数, 病床数, 病院区分)
県テーブル (id, name)

これで地域テーブルである地域を指定した場合は、それに所属する消防署と病院が見られる。病院は消防署から近い(直線距離)5件程度を採用(本当は2次医療圏でやりたいけど)
利用病院テーブル(id, 消防署id, 病院id)

これで形を整えられそう。
(追記)
以前作った2次医療圏テーブルを確認したら、それぞれ地域idを持っていたのでそのまま使えそう。 地域テーブルに2次医療圏IDを追加し、2次医療圏テーブルを作成する。今回のテーブル構成は結局下記で実施する。

消防署テーブル(id, 消防署名, 地域id(5桁), 緯度, 経度, 種別, 住所)
地域テーブル(id(5桁), 地域名, 人口, その他の情報(人口比率など), 県コード, 2次医療圏id)
病院テーブル(病院ID, 病院名, 地域id(5桁), 住所, 緯度, 経度, 医師数, 病床数, 病院区分)
2次医療圏テーブル(id(4桁), 2次医療圏名, その他情報) 
県テーブル (id, name)

 情報の提示は次のフロー
1.2次医療圏を選択 -> それに紐づく地域が表示
2.地域を選択 -> その地域の消防署と病院が表示