# 道路ネットワーク上の移動モデルを作成する① 本節では、実際の都市空間への適用を想定した、道路ネットワーク上での移動モデルを作成します。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)