先週始めたアルバイト?インターン?で、GISソフトを用いたので、復習を兼ねてODPTデータに含まれる駅を地図上に描画します。
PostGIS など、学びたての為、誤りを含んでいる可能性があります。間違いが含まれている場合は、お手数ですがコメント又は修正して頂けると幸いです。
環境
今回用いる環境は、次の通りです。
- Windows 10 Home(クライアント)
- CentOS 7(サーバー)
また、利用するソフトは次の通りです。
- pgrouting (ドッカーコンテナイメージで利用。サーバー側で使用。)
- pgAdmin4 (CentOS側に入れてもいいとは思うのですが、Winedowsに入れました。)
予め、これらのは利用できるようになっているとします。
PostgreSQL へのデータ登録
路線情報をデータベースにインポートしていきます。
今回、データインポートは、Goを用いて行いました。
apikey
には、ODTPから取得したAPIキーを利用してください。
また、dbinfo
には、PostgreSQLのデータベース情報を入力します。
ODPT の APIから JSON を取得し、 PostgreSQL に登録するプログラムです。すでにテーブルが存在する場合、削除して作り直しますので、ご注意ください。
※ 正しく動作しない場合は、お手数ですがコメントください。
package main
import (
"time"
"strings"
"encoding/json"
"fmt"
"io/ioutil"
"log"
"net/http"
"bytes"
"database/sql"
_ "github.com/lib/pq"
)
// go get github.com/lib/pq
const apikey = "[ODPT API Key]"
const dbinfo = "host=[hostname] port=[dbport] user=[username] password=[password] dbname=[dbname] sslmode=disable"
type Operator struct {
Id string `json:"@id"`
Type string `json:"@type"`
Date string `json:"dc:date"`
Context string `json:"@context"`
Title string `json:"dc:title"`
SameAs string `json:"owl:sameAs"`
}
type Railway struct {
Id string `json:"@id"`
Type string `json:"@type"`
Date string `json:"dc:date"`
Context string `json:"@context"`
Title string `json:"dc:title"`
SameAs string `json:"owl:sameAs"`
LineCode string `json:"odpt:lineCode"`
Operator string `json:"odpt:operator"`
StationOder []StationOderElement `json:"odpt:stationOrder"`
}
type StationOderElement struct{
Index int `json:"odpt:index"`
Station string `json:"odpt:station"`
}
type Station struct{
Id string `json:"@id"`
Type string `json:"@type"`
Date string `json:"dc:date"`
Lat float64 `json:"geo:lat"`
Context string `json:"@context"`
Title string `json:"dc:title"`
Lon float64 `json:"geo:long"`
SameAs string `json:"owl:sameAs"`
Railway string `json:"odpt:railway"`
Operator string `json:"odpt:operator"`
StationCode string `json:"odpt:stationCode"`
}
func get_operator(){
// Connect Postgre SQL
db, err := sql.Open("postgres", dbinfo)
defer db.Close()
if err != nil {
fmt.Println(err)
}
// Create table
db.Query("DROP TABLE odpt_operator;")
db.Query("CREATE TABLE odpt_operator (Id text,Type text,Date text,Context text,Title text,SameAs text)")
// Create table
db.Query("DROP TABLE odpt_station;")
db.Query("CREATE TABLE odpt_station (Id text,Type text,Date text,Lat real,Context text,Title text,Lon real,SameAs text,Railway text,Operator text,StationCode text)")
// Access web
url := "https://api-tokyochallenge.odpt.org/api/v4/odpt:Operator?acl:consumerKey="+apikey;
res, err := http.Get(url)
if err != nil {
panic(err)
}
defer res.Body.Close()
body, err := ioutil.ReadAll(res.Body)
if err != nil {
panic(err)
}
buf := bytes.NewBuffer(body)
text := buf.String()
// JSON Decoding
var ttttt []Operator
if err := json.Unmarshal(([]byte)(text), &ttttt); err != nil {
log.Fatal(err)
}
for _, p := range ttttt {
// insert
db.Exec("insert into odpt_operator (Id,Type,Date,Context,Title,SameAs) values ($1,$2,$3,$4,$5,$6)",p.Id, p.Type,p.Date,p.Context,p.Title,p.SameAs)
fmt.Printf("%s->%s\n", p.SameAs,p.Title)
time.Sleep(time.Second*1)
get_station(p.SameAs)
}
// SELECT
rows, err := db.Query("SELECT SameAs,Title FROM odpt_operator")
if err != nil {
fmt.Println(err)
}
for rows.Next() {
var e Operator_
rows.Scan(&e.SameAs, &e.Title)
// es = append(es, e)
e.SameAs=strings.TrimSpace(e.SameAs)
e.Title=strings.TrimSpace(e.Title)
fmt.Printf("%s_%s\n", e.SameAs,e.Title)
}
}
func get_station(operator string){
// Connect Postgre SQL
db, err := sql.Open("postgres", dbinfo)
defer db.Close()
if err != nil {
fmt.Println(err)
}
// Access web
url := "https://api-tokyochallenge.odpt.org/api/v4/odpt:Station?odpt:operator="+operator+"&acl:consumerKey="+apikey;
res, err := http.Get(url)
if err != nil {
panic(err)
}
defer res.Body.Close()
body, err := ioutil.ReadAll(res.Body)
if err != nil {
panic(err)
}
buf := bytes.NewBuffer(body)
text := buf.String()
fmt.Printf(text)
// JSON Decoding
var ttttt []Station
if err := json.Unmarshal(([]byte)(text), &ttttt); err != nil {
log.Fatal(err)
}
for _, p := range ttttt {
// insert
db.Exec("insert into odpt_station (Id,Type,Date,Lat,Context,Title,Lon,SameAs,Railway,Operator,StationCode) values ($1,$2,$3,$4,$5,$6,$7,$8,$9,$10,$11)",p.Id, p.Type,p.Date,p.Lat,p.Context,p.Title,p.Lon,p.SameAs,p.Railway,p.Operator,p.StationCode)
}
}
func get_railway(){
// Connect Postgre SQL
db, err := sql.Open("postgres", dbinfo)
defer db.Close()
if err != nil {
fmt.Println(err)
}
// Create table
db.Exec("DROP TABLE odpt_railway;")
db.Exec("CREATE TABLE odpt_railway (Id text,Type text,Date text,Context text,Title text,SameAs text,LineCode text,Operator text)")
db.Exec("DROP TABLE odpt_railway_edge;")
db.Exec("CREATE TABLE odpt_railway_edge (StationFromIndex integer,StationFrom text,StationTo text,Railway text,isoutbound boolean)")
// Access web
url := "http://api-tokyochallenge.odpt.org/api/v4/odpt:Railway?acl:consumerKey="+apikey;
res, err := http.Get(url)
if err != nil {
panic(err)
}
defer res.Body.Close()
body, err := ioutil.ReadAll(res.Body)
if err != nil {
panic(err)
}
buf := bytes.NewBuffer(body)
text := buf.String()
// JSON Decoding
var ttttt []Railway
if err := json.Unmarshal(([]byte)(text), &ttttt); err != nil {
log.Fatal(err)
}
for _, p := range ttttt {
// insert
db.Exec("insert into odpt_railway (Id,Type,Date,Context,Title,SameAs,LineCode,Operator) values ($1,$2,$3,$4,$5,$6,$7,$8)",p.Id, p.Type,p.Date,p.Context,p.Title,p.SameAs,p.LineCode,p.Operator)
for ind := range p.StationOder{
if ind==0{
continue
}
// insert
station1 := p.StationOder[ind-1].Station
station2 := p.StationOder[ind].Station
station1index := p.StationOder[ind-1].Index
station2index := p.StationOder[ind].Index
_, err1 := db.Exec("insert into odpt_railway_edge (StationFromIndex,StationFrom,StationTo,Railway,isoutbound) values ($1,$2,$3,$4,$5)", station1index,station1,station2,p.SameAs,true)
if err1 != nil {
log.Fatal(err1)
}
_, err2 := db.Exec("insert into odpt_railway_edge (StationFromIndex,StationFrom,StationTo,Railway,isoutbound) values ($1,$2,$3,$4,$5)", station2index,station2,station1,p.SameAs,false)
if err2 != nil {
log.Fatal(err2)
}
}
}
/* // SELECT
rows, err := db.Query("SELECT SameAs,Title FROM odpt_railway")
if err != nil {
fmt.Println(err)
}
for rows.Next() {
var e Operator_
rows.Scan(&e.SameAs, &e.Title)
// es = append(es, e)
e.SameAs=strings.TrimSpace(e.SameAs)
e.Title=strings.TrimSpace(e.Title)
fmt.Printf("%s_%s\n", e.SameAs,e.Title)
}*/
}
func main() {
get_operator()
get_railway()
}
type Operator_ struct{
SameAs string
Title string
}
実行すると、下の画像のように、データベースに登録されます。
このプログラムでは、次のようなテーブルを作成します。
テーブル名 | 表すもの |
---|---|
odpt_operator | 運行会社情報 |
odpt_station | 駅情報 |
odpt_railway | 路線情報 |
odpt_railway_edge | 駅接続情報 |
Post GIS のインストール
続けて、Post GIS のインストールを行っていきます。
データベースで次のSQLを実行し、PostGISをインストールします。
CREATE EXTENSION postgis;
Geo情報の作成
PostGIS では、geometryという地理空間情報1つで記憶されています。なので、緯度や経度といった情報をgeometry型に変換します。geometry型には、次のような種類があります。
名称 | 形式 | 表しているもの |
---|---|---|
POINT | (0,0) | 単一の点 |
LINESTRING | (0 0,1 1,1 2) | 単一の折れ線 |
POLYGON | ((0 0,4 0,4 4,0 4,0 0),(1 1, 2 1, 2 2, 1 2,1 1)) | 単一の多角形 |
MULTIPOINT | ((0 0),(1,2)) | 複数点 |
MULTILINESTRING | ((0 0,1 1,1 2),(2 3,3 2,5 4)) | 複数線 |
MULTIPOLYGON | (((0 0,4 0,4 4,0 4,0 0),(1 1,2 1,2 2,1 2,1 1)), ((-1 -1,-1 -2,-2 -2,-2 -1,-1 -1))) | 複数多角形 |
GEOMETRYCOLLECTION | (POINT(2 3),LINESTRING(2 3,3 4)) | ジオメトリ コレクション |
※www.finds.jpより。Qiitaでもまとめてくださっている方がいます。
また、これらのデータを扱うことのできる関数が複数用意されています。
様々な関数一覧は、PostGISリファレンスをご覧ください。
Geometry の付与
駅情報への Geometry を付与します。Geometry の付与は、ST_SetSRID関数を利用することで、下記の SQL 一行で行えます。
SELECT SameAs, ST_SetSRID(ST_Point(lon, lat), 4326) FROM odpt_station;
SELECT された結果を開いてみましょう。
さらに、列名の右側に表示されている目のようなアイコンをクリックすると、下記のように、地図上にプロットされます。
首都圏を拡大してみましょう。
駅が正しく描画されていることが分かります。
なお、今回は省略しますが、ST_MakeLine
などの関数を用いることで、路線の線を描画することもできます。
GTFS もプロットしてみる
因みに、GTFSデータをインポートすることで、GTFSをマップ上に描画することもできます。データベースへのインポート手順はGTFSファイルをマージしてPostgreSQL+PostGISに投入する方法などをご覧ください。GTFSの場合は、下のようなSQLで、 Geometry を付与したテーブルを作成することができます。
AS SELECT stop_id, ST_SetSRID(ST_Point(stop_lon, stop_lat), 4326) FROM stops;
オーストラリア・ブリスベンの TransLink の GTFS に含まれる stop をプロットすると、次のようにして描画されます。
縮小図
拡大図
データベースからWebクライアント上でそのまま描画できて便利ですね。