# 道路ネットワーク上の移動モデルを作成する①
本節では、実際の都市空間への適用を想定した、道路ネットワーク上での移動モデルを作成します。artisocでは、交差点部分をノード、道路部分をリンクとして読み込むことで、道路ネットワークを表現します。
## モデルの概要
例として、下図のようなシンプルな道路ネットワークを考えます。前節ではノードは人、リンクは人の繋がりでしたが、本節ではノードは交差点、リンクは交差点を繋ぐ道路を意味します。
```eval_rst
.. image:: images/4-1/image_tutorial4-1-1.png
:align: center
:scale: 80%
```
本節では、テキストファイルで定義された道路ネットワークデータを読み込むことで、モデル上にネットワークを作成します。さらに、そのネットワーク上で人がランダムに動くルールを作成しましょう。
それでは、モデルを作成していきます。まずは、ネットワークを読み込むための下準備をします。
- 「新規モデルの作成」から新しくモデルの画面を開き、モデルには「道路モデル」と名前を付けましょう。
- 次にルール画面を表示し、モデルツリーのUniverseの下に空間を追加します。空間名は「city」、他はデフォルトのままにしておきましょう。
- 空間cityの下には「node」という名前でエージェントを追加します。
- エージェントnodeの変数としてnode_numberとlinkを作成します。
- ユニバース変数「all_nodes」を作成します。
- マップの出力設定で、空間cityを出力します。マップ要素リストにエージェントnodeを追加します。さらに、エージェント表示設定でエージェント間に線を引く設定を行います。対象の変数としてlinkを選択してください。
## ファイルからネットワークのノードを読み込む
### ファイルの作成と登録
まずはネットワークのノードを表すファイルを作成し、それを読み取ってネットワークを作成します。ファイルの読み込みは様々な場面で使えるので、やり方をマスターしておきましょう。
以下のような「node.csv」を用意します。このデータにはノード番号とxy座標を定義しています。ノード0番の座標は(20,40), ノード1番の座標は(40, 40), ... といった具合です。
```eval_rst
.. list-table::
:header-rows: 1
:widths: 6, 6, 6
:align: center
* - node_number
- x
- y
* - 0
- 20
- 40
* - 1
- 40
- 40
* - 2
- 20
- 20
* - 3
- 35
- 20
* - 4
- 10
- 10
* - 5
- 20
- 5
```
エクセルで作成する場合は、以下のような画面になります。保存時にファイルの種類として「csv(コンマ区切り)」を選択することに注意してください。
```eval_rst
.. image:: images/4-2/image_tutorial4-2-2.png
:align: center
:scale: 100%
```
```eval_rst
.. image:: images/4-2/image_tutorial4-2-3.png
:align: center
:scale: 100%
```
作成したファイルをモデルに登録しましょう。編集画面左下の「入力ファイル」画面の「+」ボタンを押して、ファイルをインポートします。
```eval_rst
.. image:: images/4-2/image_tutorial4-2-4.png
:align: center
:scale: 40%
```
### ファイルの読み込み
次に、このcsvファイルをルールエディタから読み込む練習をします。univ_initに以下のようなルールを書き込んでください。
```
def univ_init(self):
import csv
with open("node.csv", mode="r") as f:
reader = csv.DictReader(f)
for data in reader:
print(data["x"])
```
ルールの意味を1行ずつ説明します。
`import csv`とは、pythonの標準ライブラリであるcsvを取り込むためのコードです。artisoc cloudはpythonをベースとしたソフトなので、pythonモジュールを利用することが出来ます。
` with open`とは、ファイルにアクセスするための関数です。引数にファイルの名称とアクセスモード(ここでは`”r”`、つまり読み込み専用)を指定し、`as`の後ろでコード上におけるファイルの呼び名(ここでは` f`)を決めています。
`reader = csv.DictReader(f)`では、ファイル`f`を読み込み、変数`reader`として返す操作を行っています。
`for data in reader:`以下では、for文を用いることで`reader`の中身を変数`data`として1行ずつ取り出しています。さらに、`print(data["x"])`でx座標のデータを出力することができます。(これについては、このあと詳しく説明します)
実行結果は以下のようになります。たしかに、各ノードのx座標が0番ノードから順番に出力されていることがわかります。
```
20
40
20
35
10
20
```
※ファイルを読み込む際、まれにコンソールに`UnicodeDecodeError`などのエラーが出る場合があります。これは、ファイルの文字コード変換に失敗した際に出るエラーです。日本語環境で作成されたcsvファイルの場合、`with open`でファイルを読み込む際に以下のように`shift_jis`か`cp932`を文字コードとして指定すると解消することが多いです。
- `with open("node.csv", mode="r", encoding="shift_jis") as f:`
- `with open("node.csv", mode="r", encoding="cp932") as f:`
### 辞書型とDictReader
ここで、変数`data`の型について説明しておきましょう。dataは辞書型(ディクショナリ型)の変数です。辞書型の変数は、キーとバリュー(値)の組み合わせで構成されています。上の例で言えば、for文の最初の繰り返しにおいて、変数`data`は以下のような形になっています。
```
data = {"node_number": 0, "x": 20, "y": 40}
```
`"node_number"`, ` "x"`, `"y"`がキー、`0`, `20`, `40`がそれぞれのキーに対する値です。「node.csv」で定義した、0番ノードの情報を持っていることが分かります。
辞書型の変数から値を取り出すためには、ブラケット`[]`でキーを指定します。したがって、以下のよう書くことでキー`"x"`の値(20)を取り出すことができます。
```
print(data["x"]) # 「20」がプリントされる
```
`csv.DictReader()`は、csvファイルの2行目以降の各行について、1行目の各列をキーとした辞書型の変数を返す関数です(より正確に言えば、2行目以降の各行を表す辞書型変数のリストを返します)。1行目にカラム名を持つ表形式のcsvデータを読み込むときに便利ですので、覚えておきましょう。
### ノードの生成
ファイルを読み込む方法を理解したので、改めて、読み込んだデータを基にノードの生成を行います。先ほどのルールを修正して、以下のようなルールを書いてください。
```
def univ_init(self):
import csv
with open("node.csv", mode="r") as f:
reader = csv.DictReader(f)
for data in reader:
node = create_agt(Universe.city.node)
node.node_number = int(data["node_number"])
node.x = float(data["x"])
node.y = float(data["y"])
```
for文の中身が変わったので、その説明をしていきます。
`node = create_agt(Universe.city.node)`で、新たなnodeエージェントを生成します。
`node.node_number = int(data["node_number"])`で、生成したノードにノード番号を割り振ります。読み込んだノード番号は、そのままでは文字列型として認識されてしまいます。`int`は、データを整数に変換するための関数です。
`node.x = float(data["x"])`で、ノードのx座標を指定します。y座標も同様です。`float`によってデータを実数値に変換します。
ここまで書いたら、保存して実行してみましょう。以下の図のように、各ノードが表示されればOKです。
```eval_rst
.. image:: images/4-2/image_tutorial4-2-7.png
:align: center
:scale: 50%
```
## ファイルからネットワークのリンクを読み込む
次に、リンクデータとして以下のような「link.csv」を読み込みます。ファイルにはリンクの始点ノード番号と終点ノード番号が含まれています。たとえば、0番ノードは2番ノードと繋がっている、という具合です。
```eval_rst
.. list-table::
:header-rows: 1
:widths: 6, 6
:align: center
* - node_number
- to_node
* - 0
- 2
* - 1
- 3
* - 2
- 0
* - 2
- 3
* - 2
- 4
* - 3
- 1
* - 3
- 2
* - 4
- 2
* - 4
- 5
* - 5
- 4
```
エクセルで作成する場合には、以下のようになります。
```eval_rst
.. image:: images/4-2/image_tutorial4-2-6.png
:align: center
:scale: 100%
```
作成したら、node.csvと同じように入力ファイルに追加しておきましょう。
エージェントのリンク関係の定義には、変数linkを用います。自分に繋がっているエージェントはここに追加されることになります。agt_initで、linkを空のset型変数として初期化しましょう。
def agt_init(self):
self.link = set() # リンクを空のset型変数として初期化
各エージェントのlinkにノードを登録するための準備として、すべてのノードをユニバース変数のall_nodesに**リスト型**の変数として格納します。リスト型とは複数のデータを順番つきで格納できるデータ型で、番号(インデックス)を指定すれば個々の要素を取り出すことが出来ます。つまり、「all_nodesのto_node番目のノードをfrom_node番目のノードの変数linkに加える」というルールを書けばよいのです。
まずはall_nodesを作成しましょう。先ほど作成したuniv_initに、以下のように2か所追記してください。
def univ_init(self):
import csv
# 追記。ユニバース変数all_nodesをリスト型として初期化
Universe.all_nodes = list()
with open("node.csv", mode="r") as f:
reader = csv.DictReader(f)
for data in reader:
node = create_agt(Universe.city.node)
node.node_number = int(data["node_number"])
node.x = float(data["x"])
node.y = float(data["y"])
Universe.all_nodes.append(node) # 追記。作成したノードをall_nodesに追加
`Universe.all_nodes = list()`で、変数`all_nodes`を空のリストとして初期化しています。
さらに、ノードを作成した後に`Universe.all_nodes.append(node)`とすることで、作成したノードを`all_nodes`に追加します。リスト型変数.append(要素)でリストの末尾に要素を追加することができます。ノードは0番から順番に作成されるので、`all_nodes`にもノードが0番から順番に格納されることになります。
この`all_nodes`を用いつつ、ファイルを読み込んでリンク関係を定義します。univ_initに以下のように追記してください。
```
def univ_init(self):
# ノードの読み込みと作成(略)
# ここから追記(リンクの作成)
with open("link.csv", mode="r") as f:
reader = csv.DictReader(f)
for data in reader:
from_node_number = int(data["from_node"])
to_node_number = int(data["to_node"])
from_node = Universe.all_nodes[from_node_number]
to_node = Universe.all_nodes[to_node_number]
from_node.link.add(to_node)
```
link.csvをDictReaderで読み込み、for文を回すところまではさきほどと同じ流れです。以下、for文の中身について説明していきます。
`from_node_number = int(data["from_node"])`で、リンクの起点ノード番号を取得します。
同様に、`to_node_number = int(data["to_node"])`で、リンクの終点のノード番号を取得します。
`from_node = Universe.all_nodes[from_node_number]`で、リンクの起点ノード(エージェント)を取得します。リスト型変数`all_nodes`の`from_node_number`番目を取得しているわけです。このように、リスト型変数から要素を取り出すためにはブラケット`[]`を用いて要素番号を指定します。なお、要素番号は1番でなく0番から始まることに注意しましょう。
同様に、`to_node = Universe.all_nodes[to_node_number]`でリンクの終点ノード(エージェント)を取得します。
最後に、`from_node.link.add(to_node)`で起点ノードの変数linkに終点ノードを追加します。linkは集合型(セット型)の変数ですので、appendではなくaddを使うことに注意してください。
これでリンクを定義できました。この状態で保存して実行してみましょう。線が引かれていれば成功です。
```eval_rst
.. image:: images/4-2/image_tutorial4-2-1.png
:align: center
:scale: 100%
```
## 参考
この章のサンプルモデルは次の通りです。
[チュートリアル4-2(道路ネットワーク①](https://artisoc-cloud.kke.co.jp/models/uhl42meZRe-hz1tW8BcGaA)