2015年11月8日日曜日

pythonの超軽量webフレームワーク Bottle:Rapiroを制御する

さて本題ですが、webでRapiroを制御できるようにします。元々、これがしたくてpythonで動く簡単なフレームワークがないか探していました。

とりあえず、作ったweb画面を以下に示します。

何のひねりもありませんが、前進、後退、右旋回、左旋回と停止を指示できるようにします。Rapiroを制御するPythonプログラムではRapiroの運動制御を行っているArudinoに、制御用コンピュータとして搭載してあるRaspberry Piからシリアルで各コマンドを送るだけです。そのコマンドを、上記ページのボタンを押したら、送ってやるようにしてやればいいだけです。

Raspberry Piに以下の様なファイル(ディレクトリ構成)を作ります。

rapiro_study/
    - bottle.py
    - index.py
    views/
        - title.tpl

bottle.pyは、フレームワーク本体です。本来ならpythonのパッケージライブラリに入れるべきなんでしょうが、1ファイルと簡単な構成なのでわかりやすいようにここに一緒に置いています。index.pyがwebを制御する本体のプログラムです。(といっても、これだけです)views/以下に、htmlを置いておきます。(このディレクトリ名は固定のようです)今回は一つしか画面を用意していないので、一つしかありません。(title.tpl)これは拡張子が"tpl"ですが、内容は全く"html"です。

title.tpl
<!DOCTYPE html>
<html lang="ja">
 <head>
  <meta charset="UTF-8">
  <title>RAPIRO</title>
 </head>
 <body>
  <h1>RAPIRO Control page</h1>

  <form name="frm" method="get" action="stop">
  <input type="button" onclick="document.frm.submit();" value="停止" style="font-size: 400%; width:200px; ">
  </form>


  <form name="frmF" method="post" action="forward">
  <input type="button" onclick="document.frmF.submit();" value="前進" style="font-size: 100%; width:100px; height:100px; position: absolute; left: 50%; top: 40%;">
  </form>

  <form name="frmB" method="post" action="back">
  <input type="button" onclick="document.frmB.submit();" value="後退" style="font-size: 100%; width:100px; height:100px; position: absolute; left: 50%; top: 60%">
  </form>

  <form name="frmR" method="post" action="right">
  <input type="button" onclick="document.frmR.submit();" value="右旋回" style="font-size: 100%; width:100px; height:100px; position: absolute; left: 70%; top: 50%">
  </form>

  <form name="frmL" method="post" action="left">
  <input type="button" onclick="document.frmL.submit();" value="左旋回" style="font-size: 100%; width:100px; height:100px; position: absolute; left: 30%; top: 50%">
  </form>
 </body> 
</html> 

index.py
#!/usr/bin/python
# -*- coding: utf-8 -*-

from bottle import route, run, template, request

'''
__debug__を追加。(通常実行時は、__debug__ = 1)
Macでデバッグしたい時は、$ python -O rapiro3.pyで最適化をかけて実行する。
'''

if __debug__:
 import serial
 com = serial.Serial('/dev/ttyAMA0', 57600, timeout=10)

# 出力を一カ所にまとめる。(デバッグ時には標準出力にだすようにする)
def out(comStr):
 if __debug__:
  com.write(comStr)
 else:
  print comStr

# localhost:8000
@route('/')
def title():
    # views/title.tplを呼ぶ
    return template('title')


# localhost:8000/show
@route('/stop', method='GET')
def stop():
    # GETパラメータの取得(username, men)
    command = request.forms.submit

    print("in stop", command)

    out('#M0') # 停止(初期姿勢)

    # views/title.tplを呼ぶ
    return template('title')

# localhost:8000/forward
@route('/forward', method='POST')
def forward():
    print("in forward")
    out('#M1') # 前進

    # views/title.tplを呼ぶ
    return template('title')

# localhost:8000/back
@route('/back', method='POST')
def back():
    print("in back")
    out('#M2') # 後退

    # views/title.tplを呼ぶ
    return template('title')

# localhost:8000/right
@route('/right', method='POST')
def right():
    print("in right")
    out('#M4') # 右回り

    # views/title.tplを呼ぶ
    return template('title')

# localhost:8000/left
@route('/left', method='POST')
def left():
    print("in left")
    out('#M3') # 左回り

    # views/title.tplを呼ぶ
    return template('title')

# ビルドインサーバの実行
run(host='rapiropi.local', port=8000, debug=True, reloader=True)

これだけです。実行するときは、Rapiroのコンピュータ(rapiropiです)にログインし、以下のコマンドを実行します。

$ python index.py

その後、PCで以下のURLをブラウザで表示します。

http://rapiropi.local:8000/

URL名の最後が".local"とちょっと普通と違いますが、家庭内のPCを特にDNSを作らなくてもURLでアクセスできるようにパッケージを入れてあるからです。Webのボタンを押したら、本来なら別のページに飛ぶためのhtmlファイルを指定するところなんですが、今回は単にpythonに制御用の指示を出したいだけなので、単純に文字列を送信して、index.pyではそれを受けとって対応するシリアルコマンドを送ったら、また一つだけのホームページを表示するようにしています。ホームページの方は文字の大きさや何かを指定するために、CSSを使わず個別に指定しています。大したページでもなかったので、CSSを作るのが面倒くさかっただけです。

実際に動かしてみたところ、「ちょっと反応が鈍いかな?」という感じですがちゃんと動いてくれました。
制御用にGETとPOSTの両方を使っていますが、単に勉強用に両方を使ってみただけです。コマンドと関係はありません。(特に送信時に個別のデータはつけてませんし)JavaScriptの練習の時にはよくPOSTで送信していましたが、いざhtmlの定義だけでやるとき、最初は"submit"ボタンしか送信する方法が思いつかず悩みました。調べていたら、ボタンにformをつけて、そこで一つだけJavaScriptを使う方法(onclick="document.frm.submit();")が簡単なので、それを多用しています。実は、中々この方法がわからず、最後はJavaScriptを各ボタン毎に書かないといけないだろうかと思いながら、でもこれはhtmlじゃなくtplのbottle用のファイルだから動くんだろうかと悩んでいました。
結局、bottleを使いながらJavaScriptも使えるようですがちょっと特別なお作法があるようです。今回は処理的なところはPythonでやりたかったので、色々調べてみました。

2015年9月23日水曜日

pythonの超軽量webフレームワーク Bottle

pythonでおもちゃを動かすプログラムを作っていて、それをwebからコントロールしたくなりました。 何か楽な手段はないかと探したら、pythonによる超軽量なwebフレームワーク”Bottle”があることを知りました。特徴は、bottle.pyというファイル一つ使うだけでいいこと。

1.bottleのインストール

インストールというほどのこともありません。以下のコマンドでbottle.pyをDLしてくるだけです。

wget http://bottlepy.org/bottle.py

ただこのコマンドを実行してみたところ、githubに飛ばされて、そこからDLしてきました。


2.最初のサンプル

bottle.pyの中に各種モジュールが入っているので、必要なものをimportしたpythonプログラムを書いてやります。超シンプルなものを以下に示します。(hello_world.pyで作成)

from bottle import route, run

@route('/hello')
def hello():
    return "Hello World!"

run(host='localhost', port=8080, debug=True, reloader=True)

これで、以下を実行します。(bottle.pyは同じ階層に置いておきます)
 $ python hello_world.py 

次にブラウザで、http://localhost:8080/hello にアクセスすると、ブラウザに”Hello World!”の文字が表示されます。 最初に文字コードを指定してやれば、日本語ももちろんいけます。

 # -*- coding:utf-8 -*-

さて、次に考えないといけないのは、ページ上に入力欄やボタン、リンクに飛ばすことができるよう、htmlを別途用意して、それを表示させるにはどうしたらいいか?どうもテンプレート(.tpl)ファイルを用意してやればいいようです。そちらについては、また今度結果をまとめようと思います。

2015年7月24日金曜日

日本の海岸線データの加工

かなり国土地理院のデータを間引いて、なんとか100MBのデータを数MBまでにしましたが、ノートPCなんかで使うにはまだまだサイズが大きいです。思い切って1/100に間引いてみましたが、日本の海岸線が途切れてしまいます。
やはり少し頭を使って、元になる国土地理院の海岸線データの特徴を調べてみます。

1.海岸線データの固まり方
先にも言いましたが、元データは県別のxmlデータになっていて、行政界、島毎にタグが分かれています。それを緯度経度だけ抽出してくると以下ののようになります。
35.04924833 136.83906528
35.05145167 136.83815250
35.05175111 136.83802834
   

35.05899417 136.87838278
35.05921944 136.87846166
35.05952000 136.87857805
35.05949694 136.87866195
35.05947139 136.87874555
35.05914306 136.87861805
35.05894917 136.87855139
35.05899417 136.87838278
   

35.05730000 136.87774750
35.05771861 136.87788778

途中、2行の空白行がありますが、そこが行政界の区切り、あるいは島のデータの始まりを表します。上記サンプルを見ると、2つ目のデータの固まりのデータの始まりと終わり(6行目と13行目です)が同じ緯度経度になっています。つまりこれは島だということです。
前回のプログラムでは、以下に注意しました。

  • 行政界、島の区切りを示す空白行は残す。
  • 空白行を出力したところで間引きカウンタを0クリアして、空白行に続く先頭データは積極的に出力する。


しかし、特に島のデータに顕著にでましたが、あまり間引く数が大きいときはデータの固まりの最後(空白行の直前のデータ)も残してやらないと島が閉曲線になりません。

2,改良版プログラム
ということで、島の最後のデータも残してやるように改修しました。
#!/usr/bin/python
# coding: utf-8

import sys

if __name__ == "__main__":
 argv = sys.argv  # コマンドライン引数を格納したリストの取得
 argc = len(argv)  # 引数の個数

# print argv
# print argc

 fi = open(argv[1], 'r')
 fo = open(argv[2], 'w')

 # n行に1回出力するようにする
 ct = 0

 preLine = ""

 for line in fi:
  if line.strip() == "": # 島等のデータとの境界である空白行は残すようにする
   if (preLine.strip() != ""): # 空白行の直前のデータは残す(島の閉曲線を守るため)
    fo.write(preLine)
   fo.write(line)
   ct = 0 # 島のデータは少ないので1行でも残す
   preLine = line
   continue

  if ct == 0:
   fo.write(line)

  ct = ct + 1
  preLine = line

  if ct == 100:  # 100行に1回出力する
   ct = 0

 fi.close()
 fo.close()

さてこれで1/100に間引いてみましたが、どうもデータのサイズが思うように小さくなりません。

3.改良版の処理結果を見る
再度、処理結果を調べてみました。
35.08614861 136.89070723
35.08342250 136.86787278
35.05776194 136.84691778
35.06403806 136.84048584
   

35.05175111 136.83802834
35.05175111 136.83802834
   

35.05899417 136.87838278
35.05899417 136.87838278

確かに島のデータは残されるようになっていますが、思いの外国土地理院のデータには小島がしっかりはいっているようで、同じ緯度経度が2回連続する固まりが大量にありました。こいつらは改良版のプログラムではどんなに間引率を大きくしても残されるようにしてあるため、思ったようにデータのサイズが小さくならなかったようです。
でも実際日本地図を使うようなときはこんな小島、表示もされないのでデータの無駄です。

4.小島のデータを削除する
手作業で削除するかとも思ったんですが、すぐに諦めました。さすがにデータのサイズが小さくならない原因だけあって大量に存在しています。あきらめて、2行同じ緯度経度のデータが続く箇所はフィルタするようなプログラムを作りました。(行政界の他のところで偶然そういう場所がないか、という恐れはありますがまあ少しくらい抜けてもそれに気づくほど拡大して使うことはないでしょう。もし使う必要があるなら、それはもう手作業でなんとかしてやるしかありません)
#!/usr/bin/python
# coding: utf-8

import sys

if __name__ == "__main__":
 argv = sys.argv  # コマンドライン引数を格納したリストの取得
 argc = len(argv)  # 引数の個数

# print argv
# print argc

 fi = open(argv[1], 'r')
 fo = open(argv[2], 'w')

 # n行に1回出力するようにする
 ct = 0

 preLine = ""

 for line in fi:
  if line.strip() == "": # 島等のデータとの境界である空白行は残すようにする
   if (preLine.strip() != ""): # 空白行の直前のデータは残す(島の閉曲線を守るため)
    fo.write(preLine)
   fo.write(line)
   ct = 0 # 島のデータは少ないので1行でも残す
   preLine = line
   continue

  if preLine != line:
   fo.write(preLine)
  else:
   preLine = ""
   line = ""
   continue

  preLine = line


 fi.close()
 fo.close()

かなり手抜きプログラムですが、これでサイズを1/100にし、かつ2行しかないような小島のデータを削除して、データサイズが1.4MBまで縮小できました。結果を以下に示します。

先日の海岸線とほとんど見た目は変わりません。よく見れば、小さな小島は消えているんですがね。




2015年7月21日火曜日

日本の海岸線データを手にいれる

レポートを書くとき、何かと使うことの多いのが地図データです。しかも緯度経度で海岸線や県境のデータがあるとベストです。(緯度経度で地点をマークして一緒にEXCELで散布図にしてやれば正確な表現ができます)
実は国土地理院では昔から公開していますが、数年前からやたら複雑な書式のxmlなので尻込みしていました。少し長い休みがあるので、重い腰をあげてデータ整理でもしてみましょう。

1.海岸線データを入手する
ぐぐれば簡単に見つかります。DLするとき何に使う?とか簡単なアンケートに答えないといけませんが、「趣味」の項目もありますから特に気張らずに。(ただ、ライセンス的にフリーというわけではないので注意はしてください。個人目的、研究用等に使うならご自由にという程度です)
データは県ごとに分かれています。

2.xmlフォーマット
各県ごとにデータが分かれています。しかも、県の中でも行政界ごとにxmlのタグが分けられているようです。以下に例を示します。

  <gml:LineStringSegment>
   <gml:posList>
35.55061306 140.11532166
35.55081833 140.11538084
35.55206806 140.11517666

xmlのタグ、<gml:posList>で識別されています。数字は一目で緯度経度とわかります。(国土地理院なので、今は当然世界測地系です)

3.海岸線データを取り出す
テキストエディタで取り出してもいいんですが、なにせ数があります。こちらは単に日本地図が描きたいだけなんですが、きちんと県別、さらに行政界毎にデータがタグで分離されているため、テキストエディタで手作業でやってたら死にます。
とりあえず、xmlなので、以下のプログラムでこのタグ部分だけ抽出してやります。

# coding: utf-8

import xml.dom.minidom

if __name__ == "__main__":

 dom = xml.dom.minidom.parse("sample.xml")

 for line in dom.getElementsByTagName("gml:posList"):
  print line.firstChild.data

以下の様な感じで海岸線のデータだけ標準出力にでてきますので、それをファイルにリダイレクトしてやります。

35.55061306 140.11532166
35.55081833 140.11538084
35.55206806 140.11517666
35.55243944 140.11512389
35.55278833 140.11507416

4.データの間引き
さてこうしてできた海岸線のデータですが、単に数値の羅列ですがなんと100MB強あります!県毎にsheetを分ければなんとか日本の海岸線を散布図で描けますが、一つのファイルにまとめてしまうと、サイズが(行数が)大きくEXCELが扱えないといってきます。どうせそんな細かいところまで見ないので、データの間引きをします。そのために用意したプログラムを以下に示します。

#!/usr/bin/python
# coding: utf-8

import sys

if __name__ == "__main__":
 argv = sys.argv  # コマンドライン引数を格納したリストの取得
 argc = len(argv)  # 引数の個数

# print argv
# print argc

 fi = open(argv[1], 'r')
 fo = open(argv[2], 'w')

 # n行に1回出力するようにする
 ct = 0

 for line in fi:
  if line.strip() == "": # 島等のデータとの境界である空白行は残すようにする
   fo.write(line)
   ct = 0 # 島のデータは少ないので1行でも残す
   continue

  if ct == 0:
   fo.write(line)

  ct = ct + 1

  if ct == 10:  # 10行に1回出力する
   ct = 0

 fi.close()
 fo.close()

今回は起動時に入力ファイル、出力ファイルを読むようにしました。あと間引く時、元のデータでは行政界毎、及び島毎に空行が入っていました。ここを無視して間引いいてしまうと、小さな島だとデータがなくなってしまいかねません。ちょっとだけ工夫しました。間引く数は実際に試して実験です。結局、10分の1(約10MB)にしないとまともにEXCELは動きませんでした。使うPC環境にもよりますが、もう少し間引いた方がいい気がします。またどうせかなりの縮尺で使うつもりなので、こだわって島のでデータを消さないようにしました。小さな島は消したいんですが、属性として地名は入っているんですが、それが島かどうかは人間が判断してやらないといけません。(GISデータは最後は人力で編集しないと、いいデータにはなりませんね(>_<))

5.完成
以下に実際に作成した日本(白)地図を示します。


使うときは例で示したように、別途示したい位置の緯度経度のデータを用意して、データ系列を追加してやります。注意が必要なのは、普通にやると追加したデータのグラフも散布図の折れ線になってしまいます。単にX-Yの座標だけの散布図にしたい場合が多いでしょうから、その時はデータ系列を追加してから、追加したデータを選択し、グラフの種類を変えてやります。(「複合グラフ」といいます)


2015年6月21日日曜日

g++における、getline()の戻り値について

cのときからテキストファイルを読み込むときによく使っていたのがgetline()でした。

ssize_t getline(char** , size_t , FILE* )

1行ずつ読込、戻り値に読み込んだ文字数が返ってくるため、ファイルの最後までいくと読めないので、文字数0が返ってきました。それを使って、最後まで読んだかをチェックするのが普通でした。

while ( getline(line, 256, fp) !=0 )

こんな感じです。c++になるとifstreamで同じ関数があり、確か最初(20年くらい前)は同じ仕様だったと思います。ずっとそう思っていました…

Macのxcodeで既存のlinuxで作成したプログラムをコンパイルしようとすると、どうしてもこのgetline()でエラーがでて、ずっと悩んでいましたが今日やっと理由がわかりました。c++でのgetline()の戻り値はifstream自身でした。
つまり、読込に成功したら"true"、最後までいき失敗したら"false"を返すようになってました。
でもこれまでlinuxの方では、0とかNULLで比較していても何もいってこなかったんで気付きませんでしたが、本当は以下のようにチェックすべきでした。

while ( fp.getline(line, 256))

linuxの方は昔のcの実装がboolがなくて、unsigned intでやれ、という指定だったためそれとの互換性を考えていたようです。しかしmacは厳密にチェックするようになったため、エラーにしているようです。さて、既存の遺産をどうするか注意しないと。


2015年6月13日土曜日

既存のgit管理されたCプログラムをeclipseで開発

久しぶりにちょっとした備忘録です。

1.概要
2.eclipseで操作する前の状態
3.eclipseで操作する状態
4.やった方が感じがいいこと

こんな感じで進めていきたいと思います。

1.概要
他のPC、或いは既にプロジェクトが少し進んでいてそれがgit管理されています。そこに参加した、或いは最初はソースコードを書いていただけで、実際のコンパイルをこれから始めるという状況です。
Linuxを想定しますが、eclipse-CDTだとC/C++プロジェクトを作成でき、しかも今はGit管理パッケージが最初から入っています。(数年前はGit管理パッケージが最初は入っていなくて、いちいちEGitを後から入れたりして大変でした)

話を簡単にするために以下の状況を想定します。

・ソースはgithub等のリポジトリにあるわけではなく、何故かローカルでPCに入れてあります。(分散リポジトリなんで、簡単に実現できます)
・~/ProgWork/work以下にgit管理されたソースが入っています。
・そこにはmain.cだけが入っています。
・当然、~/ProgWork/workには .git ディレクトリがあります。

つまり、このgit管理されたものをeclipseで開発を(gitを使いながら)継続したいというわけです。

2.eclipseで操作する前の状態
とりあえずデフォルトの状態として、eclipseの作業ディレクトリは、$HOME/workspaceとしておきます。最初はもちろん何もプロジェクトがなく、空っぽとします。(注:でも最新のeclipseだと、workspace/RemoteSystemTemFilesなんてディレクトリができていました。何でしょこれ?)

3.eclipseで操作する状態
やることは以下の通りです。

(1)eclipseでFileメニューから"Import"を選択する。
(2)選択Dialogが表示されます。その中に、"Git"があります。"Projects from Git"を選択してNextします。
(3)次のDialogで"Existing local repository"というのがでてきますので、これを選択します。(注:ちなみに通常は外部のgithub等からプロジェクトをとってきますから、もう一つの"Clone URI"を選択します。こっちのやり方は以前にも書きましたのでここでは省略します。)
(4)ここで、~/ProgWork/work/.gitを選択します。
(5)後はproject wizardに進みますので、そのまま作業を進めればeclipseで開発が続けれます。
(6)ただこれではプロジェクトを作っただけで、まだ肝心のソースファイルがプロジェクトに入っていません。Fileメニューで作ったプロジェクトにソースファイルを追加してやります。
(7)ただこれだけではまだ、このプロジェクトはGit管理できません。eclipseのTeamメニューでもいつものGit関係のコマンドがでてきません。Teamメニューから"Share project"をしてやる必要があります。

ところでこの状態でディレクトリの状態はどうなっているんでしょう?ここからは実は2通りあります。今回ブログを書くにあたって実験してたら、2通りの状態になってしまいました。どちらが正しい状態かはわかりませんが、どちらも動くようなのでたまたまなのでしょう。

"sample"というc/c++のプロジェクトを作りましたが、~/ProgWork/work/sampleというディレクトリが作成されました。ビルドしたバイナリはここにできます。
問題は、eclipseが管理する、".project"なんですが、これが~/ProgWork/work/sampleにできる場合と、eclipseのデフォルトの、~/workspace/sampleにできる場合の2通りにわかれました。
どちらの場合でも、結局元のソースは~/ProgWork/work/以下にあり、eclipseからは普通に操作できますし、gitでcommitもできます。
(実は、自分はここでworkspace/sample以下にコピーが作成されてしまい、eclipse独自のgit管理に入ってしまうかと予想していましたが、そんなことはありませんでした^^;)

4.やった方が感じがいいこと
さて、残る問題はeclipseによって勝手に作成される、~/ProgWork/work/sampleというディレクトリです。これがgit管理下には入っていませんから、よくgitから文句を言われます。
こんなときは、~/ProgWork/work/.gitignoreファイルを作り、そこに"sample/"と文字列を追加しておけば、gitはそれは自分の管轄外と思ってくれて無視してくれます。
ただ、さらにいうと今度は".gitignore"も管理に入れろとgitは文句をいってきます。これを.gitignoreに含めるかは、宗教の問題になってきます。会社などで同じ開発環境で作業しているなら、.gitiginore自体も共有すべきだというのが、通常の考え方なんだそうです。ただまあこれは好みの問題で、自分の様に個人で遊んでいる場合には.gitiginoreに自身も含めてしまっています。
(だって、"git status"で毎回なんか文句言われると気になるから)


2015年5月24日日曜日

テーブルデータのグラフ表示

さてCouchDBを使って、テーブルデータを扱うhtmlはできましたが、まだ表だけです。やはりここはグラフ表示できないと使い物になりません。そこでjCanvasを使ってグラフ表示できるようにしてみました。 まずデータの読込です。

ここで1つデータを追加します。
とまあ、順調にできました。
いつもならここでコードを参考に載せるところですが、機能的にまだあまりに不足している部分があるので、今回はパスします。こういう汎用的なものなら、以下の機能が欲しいところです。
・グラフを描画するcanvas要素のサイズに応じて描画。
・グラフ描画の際の上下左右のマージンをパラメータ化。
・あと、グラフの軸に目盛りが欲しい!
はっきりいって「とりあえず描画できるのを確認した」というレベルのコードしかまだできていません。( ;´Д`)まだ使い物になるかわかりませんが、少し様子をみて使い物になりそうなら、コードを作り込んでからまたブログに載せます。