GPSログをATGoogleMapsで(Plone実装編)
GPXログファイル解析エンジンもあらかた作って・・・いよいよ鬼門であるPloneプロダクト作成(というか改造)に着手・・・したメモ書き。
【環境】
- Zope-2.8.6-final (Linux)
- Plone-2.1.3 (Linux)
- PyXML 0.8.4(プラットホーム非依存のやつ)
- FileSystemStorage-2.5.1
- ATGoogleMaps 0.5.2 GPX-SP beta1 (やっと出来た 。・゚・(つД`)・゚・。 )
テスト段階では前回と同様にwindowsの環境でやってますた。
妄想から、解析エンジン作成を経て鬼門というか最大の難関であろうPlone Productsとしての実装。blogにもちょっと書きましたが結構いい感じに出来てきてるかなと思います。
これは、そのPlone Product作成との壮絶なる戦闘の記録であr・・・・なんて、単なるメモ書きです(笑
Field、Widgetってなんだ?
・・・ここからつまずくorz 取りあえずFieldちゅーのは入力したてきすととか数値とか保存しとく場所っぽい。Widgetってのは・・・編集ページで入力するフォームとか、表示画面でデフォルトの表示をしてくれるのをまとめてくれるもの?・・・((((多分)))。
でも、今回は編集人が各データを入力してうpして保存って訳じゃあないんですよね(;´Д`) はてさてどうしたもんか。取りあえずGPXファイルを保存しておくFileFieldは必須。これは結構デカイファイルなので、StorageはFileSystemStorageにしておく(ATPhotoまんまパクリ)。
あとは・・・てくすと位は必須かなぁ。紆余曲折の末、どーいうFieldになったかはソースを参照してくださいな。
あ。そうそう。ComputedFieldってのをGPXファイル解析後のデータ取得に使おうといろいろ試したんですが、なんだか訳の分からんエラーが出まくって却下しましたorz なんかの指定がおかしいのかそうじゃないのか原因はさっぱり・・・。
GPXファイルアップロード時にどーやって解析エンジンを動かすか
既存コードのATPhoto(ATImage)とか、ATAudioとか参照(というかパクり)してTry&Errorしまくった結果以下のことが判明。
Schemaに各Fieldを設定するときに指定しているField名の先頭文字を大文字にした関数をオーバーロードすることにより、各Fieldを保存するときに動かせるコードが追加できる。
GPolylineSchema = ATContentTypeSchema.copy() + Schema((
FileField('gpxlogfile',
required=1,
validators=MaxSizeValidator("checkFileMaxSize",
maxsize=MAX_FILE_SIZE),
storage = FileSystemStorage(),
widget=FileWidget(
label="GPX Log File",
label_msgid="label_gpxlogfile",
description=' Drow polyline in Google Maps.',
description_msgid="help_gpxlogfile",
i18n_domain="googlemaps"
)
),
),
)
class GPolyline(ATCTContent, HistoryAwareMixin):
-----(略)----
security.declarePrivate('setGpxlogfile')
def setGpxlogfile(self, value, **kw):
##### ここに解析エンジンを動作させるコードを入れて
##### 各Fieldも一緒に更新しちゃう。
# call fields setter
f=self.Schema()['gpxlogfile']
rtn =f.set(self, value, **kw)
ということで、このsetGpxlogfile( ) にで解析エンジン動作ロジックを埋め込んだ。新規生成(うp前)にも動いたので判定文を突っ込み、この時点で他のFieldも更新したいので各Fieldの setXxxx() もそれに合わせて。
解析結果をドコに保存するか
GPX解析エンジンよる解析結果(Object Typeはdict)保存はすぺしゃるなFieldを作ってそこに保存するべきなんでしょうけど、作り方がさっぱり分からん(しかも編集画面フォームで入力して保存するというFieldでもないし)、解析結果は ATImage の getEXIF() でやってるcache制御をパクって保存。
あと、解析結果は結構膨大なデータ量になるので、そこに頻繁にアクセスしにいくのもなんなんで、走行データ(走行距離とか平均速度とかの現存のFieldで事足りるデータ)の一部をStringFieldとかIntgerFieldとか作って保存してみると。
【結果】
大丈夫っぽい。 Zopeをリブートしたあとでもcacheがない処理へは行かなかった。取りあえずこれでいこう。
走行データの各Fieldは編集タブで入力して保存という通常の使い方ではなく、GPXファイルアップロード時に勝手に解析結果を生成して勝手に保存するので、Fieldのwidget指定のところで「visible」を↓のようにしてやったらうまくいった。Contents新規作成時にもInitializeが走らないし編集画面でも対象の入力Formが出てこない。
StringField('total_distance',
default='No Data',
widget=StringWidget(
label='Total Distance',
label_msgid='label_total_distance',
description_msgid='help_total_distance',
i18n_domain='googlemaps',
visible = {'edit':'invisible', 'view':'visible'},
modes = ('view',),
)
),
でも、解析結果ってファイルサイズに比例するからこれをバコバコcacheに入れてるとZODBのサイズ的にどうなるかかなり心配なんですな~これがorz
portal_skinsにある画像Objectをどうやって取ってくるのさ?
テストコードでは高度、速度グラフ作成時に、「0...10...20 (km/h)」 とかの目盛数値を他のPNGファイルを Image.load() で読み込んでそこから Image.crop() → Image.paste() でコピペして作ってました。なのでplone上では portal_skinsに同じ画像を入れて、グラフ作成時にそこから画像データを読み込んで同じようなことが出来るかなと思ってたんですが・・・。
断念。ムリ。portal_skinsに入っている画像取得方法がてんで分からん。Zopeでwebを作ってたときに使ってた restrictedTraverse( ) で '/portal_skins/googlemaps/画像id' とか '/画像id' 指定で試みましたが KeyError とかで怒られちゃって諦めました。
【結果】
コピペ処理は諦め、PILの ImageDraw.line() 、polygon() を使って無から作成するしかなさそう。これだと描く座標指定で死にそうですが仕方がない・・・orz
GoogleMaps API ライン描画マジック for IE6.0
あらかた出来てきて、FFでGoogleMaps上へのpolyline描画は問題なく出来てきたのですが、IEでやるとJavascript errorが発生する事態に。(←)
でもこれってGoogleからダウソしてきてるAPI群が入っているscript fileの中で発生してるんですよね・・・。(;´Д`)
こっちで作ったscriptから引数を渡すところで間違えてるのかなぁとフルチェックしましたがそんなことはない。というかそれだったらFFでも起こるはずなんだけどなぁ。
で、いろいろ調べていくと、GoogleMaps APIはライン描画に「VML」っちゅーものを使ってるっぽい。アホカヴォケと問いつめたくなるが使ってるんだから仕方がない。散々迷いましたが、ploneの基幹となるPage Templateである「/portal_skins/main_template
」をベースにATGoogleMaps用のmain_tamplateを作成して、GoogleMapsを表示する全コンテンツはこのtemplateを使ってレンダリングさせることに。
■ /portal_skins/atgooglemaps/atgooglemaps_main_template
<metal:page define-macro="master">【結論】
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" ← このDOCTYPEと
"http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd"> ←
<metal:block define-slot="top_slot" />
<metal:block use-macro="here/global_defines/macros/defines" />
<html xmlns="http://www.w3.org/1999/xhtml"
xml:lang="en"
lang="en"
xmlns:v="urn:schemas-microsoft-com:vml" ← このxmlnsの指定
tal:attributes="lang language;
xml:lang language">
・
・
・
<metal:styleslot fill-slot="style_slot">
<tal:comment replace="nothing">
A slot where you can insert CSS in the header from a template
</tal:comment>
<metal:styleslot define-slot="style_slot" />
<style type="text/css"> ← あとはよく分からん
v:* { ← この指定
behavior:url(#default#VML); ←
} ←
</style> ← ↑の太文字以外は一緒
</metal:styleslot>
・
・
・
さすがマイクロ糞フト。というかGoogleMaps APIもこんな仕様を使うってのも問題があるような気もするが。とりあえず変な仕様は使わんでくれ。
GPXファイルがおかしいときは?
エラーっぽいGPXファイルを用意して実際に動かしてみると、PyXMLでこのファイルはおかしいとはじかれるものが結構あった。GPXファイル内に埋め込まれている xsi:schemaLocation で指定されてるフォーマットに沿ってないってところか。
それ以外はなんとか自前ロジックでこんなファイルしらんってことに出来てる・・・っぽい。
Pythonのデバッグってどーやんの・・・
最初はlogをぶちこんでちまちまやってたのですが、いろいろ調べて見ると有りましたね~~~pdbが。これはいいものだ。はじめて使ったときは感動もんでした。今までのデバッグ方法は一体なんだったんだとw
ここからの動きを見たい!ってところに↓の2行を入れ込めばそこからステップ実行やら、変数の中身を見るやら出来ちゃうんだから大したもんです。これで、ArchetypesとかATContentsTypesとかの親クラスや、パクりたい処理があるProductの動きも分かるからすんごくハッキングしやすい。
というかATContentsTypesとかのAPI仕様書(英語・・・)を見るよか遥かに効率がよかった・・・orz
import pdb
pdb.set_trace()
GGPXPolylineコンテンツ以外の機能追加
主にGMapに関することですかな。
- GGPXPolylineと同様、GMap配下にGMarker、GGPXPolylineがある場合はそのGMapで全部の緯度経度から中央を算出して、MapCenterとして表示するように。
- GMapで出てるkmlファイル作成のPage TemplateもGGPXPolylineも入れた情報を出力するようにしてみました。
- Anonymous以外の人が観覧している場合はGMapセンターの位置を表示するようにしようかと。Polylineに沿ってMarkerを設置したい場合はこれでかなり楽になるはず。
さて、次は・・・
と言うわけで、取りあえずプロトタイプできあがり、ローカルテスト環境なWindowsベースから本鯖Linuxへと移行してこんな感じ(←)に出来上がりました。
下に実際のやつを作ってみて関連コンテンツとしてとしてリンク張ってます。
あとはちょこちょこ手直しとチューニングですかな。今のところ考えてるのは以下の項目。
- 1.GPXログ間引き処理実装
1,000point以下のログであれば問題はないんですが、2,000~3,000pointとなるともうあっぷあっぷ。GPSトラックログの間引き方法は未だ不明ですが、これがないと多分お話にならんと思う。・・・あぁ・・・また中学、高校の数学からお勉強し直さないとorz(三角関数とか微分・積分とか)
- 2.速度グラフ、高度グラフのY軸方向の値を平均化
GPSログって地図上でみれば一見綺麗に取れてるんですが、緯度経度の差と時間差から各ポイント間の時速を割り出してみると40km/hで走ってるところにいきなり120km/hのポイントがぼんと出たりと結構バタつきがある。NMEA生ログの場合はどうかは分かりませんが、今題材にしているGPXファイルは少なくともそんな感じ。
現在はその速度のままグラフに反映しているのでばたついてて訳分からん状態。んなもんで単位時間あたりか距離毎に平均値を取ってグラフにしようと。・・・高度グラフは・・・どうしようかなぁ。kmlファイル作成時にもこのパラメータを使うしそのままかも。
- 3.測定開始時のバタつきログ消去
GPS測定開始時の衛星捕捉状況で実際の状況としては止まってるんですけど、ログ的にはあっちこっちに飛んでてバタバタしてるところが結構あります(これはどーしようもないんですが)。
ここで有らぬ方向へ飛んでマッハを越える最高速度がマークされることもしばしば。なのでどうにかして判定して消したいなぁと。これが出来れば2.にも関連して速度系がもうちょっと正確に出ると思う。
- 4. GPXログ解析処理起動ポイントの変更
1.や2.で間引き率とか平均化する単位時間か距離を編集タブで入力して与えてやりたいので変えないとダメかなと。
Schemaに入れる各Fieldの順番を変えて、GPXログファイルを格納するFileFieldを一番最後に持ってくればすべてのFieldが更新された状態でFileFieldのset関数が動くっぽいんですが、ファイルをうpしなくとも間引き率を変えただけでも解析をやり直さないとダメなので、更新された内容を総合的に判断して解析を走らせるようにしないとイカン。・・・まずはハッキングからorz
- 5.kmlファイル作成時のパラメータチューニング
Google Earthで使うkmlファイルの仕様を見てみると各<Placemark>タグに入れてる<LookAt>タグ内のパラメータがなんだかいろいろ有ることが判明。
<LookAt>
<heading>0.0</heading>
<tilt>57.0</tilt>
<range>600.0</range>
<latitude>33.0</latitude>
<longitude>131.0</longitude>
</LookAt>この<LookAt>で、そのマーカーやらラインやらを選択したときにGoogle Earth上ですっ飛んでってくれるときの位置やら見方を指定するんですが、現状は<latitude>と<longitude>だけ指定してあとは固定値。なのでこの辺も算出して指定できないかなと。
- 6.クラス構成の見直し&Pythonコード的にもうちょっと・・・
うーん。現状はとてもクラス構成が汚いですw GPX解析結果は一つのクラスが司るべきなんでしょうけどぜんっぜんそんなふうにしてないし。pythonコードも基本的な構文しか使ってないのでリファレンスで調べてもうちょっとなんとかならんもんかと。
- 7.GPS情報入り画像ファイル用「GImageMarker」を新規作成
GPXログファイルだけじゃ飽きたらず。こんなことを考えてみました。概要としてはEXIF内にGPS位置情報がある画像をうpすればその位置情報からMarkerを作成してGoogleMaps上にぺとっと張るってな感じです。
EXIF情報の解析は既存のATImageでもやってるので、ATImageをベースにしてやればそんなに難しくはないと思いますが・・・Ploneに同梱されている exif.py は依然バグっててそのままではGPS情報が取れない。なので ATPhotoに関連して修正したヲレ専用 exif.py を使ってやればなんとかなるかも・・・。
以上。今後の野望をつらつら書いてみましたw
さて、現状のべーたバージョンなPruductファイル一式を↓の関連コンテンツに添付してみました。でもこれ単体では動かないので以下のプロダクトなどが必須なのです・・・。
- PyXML 0.8.4 ※多分必須。Ploneに同梱してるっぽいんですけどなんか動かなかった。
- FileSystemStorage ※必須。config.pyで使用する/しないを定義しようかと思いましたがメンドクサイ・・・
- Plone Lightbox JS ※無くても動きますが、imgへのリンクはコレ用にしちゃったのであったほうがよさげ。
【参考URL】
- ・Ploneプロダクト作成関係
- RichDocument How Toの翻訳
Archetypes Quick Reference Manual — plone.org
API Documentation - ・ デバッグ関連
- Debugging — plone.org
9.1 デバッガコマンド - ・vml関連
- とほほのVML入門
SVGとVML - ホームページ制作会社を経営していて思うこと - ・kmlファイル関連
- KML について - Google Earth ユーザー ガイド
Bubble://ちずろぐ/別館/ - B-Wiki - KML - ・その他
- Zope Page Template リファレンス — Webcore株式会社