ダイヤモンドトレイルを走ってきました。

11/4に開催されたダイヤモンドトレイルランに参加してきました。
あらかじめお断りしておきます。今回の記事は写真はありません。本当にそれどころじゃなかったので。
今回は久々のレース(5月の弘法トレイル以来)です。弘法トレイルのダメージが大きく(特に腰と膝)、しばらく休養することにして、しばらくトレーニング量を落として、また今年の夏は記録的猛暑で長い距離も走れず、秋口はいつものように蜂を避けるためにトレイルに入ってなかったので、トレイルに入るのも比叡山国際トレイルランのコースの下見をした8月以来ということになりました。

スタート地点は近鉄南大阪線当麻寺駅近くの農林漁業体験実習館。スタート時間は8時35分。乗換案内を調べると、7時半ごろに着く電車と7時45分ごろに着く電車がある。7時半に着くのは最寄り駅を5時台初の電車に乗らないと間に合わない。当麻寺駅からスタート地点までは徒歩20分くらいということなので、遅い方の電車だと結構余裕がないが、5時台の電車に乗るのはさすがにしんどいので7時45分着の電車で行くことにした。
環状線天王寺で降りて、近鉄阿部野橋駅まで行き、河内長野行きの準急に乗る。乗換案内では途中の古市で橿原神宮前行きに乗り換えということになっていたが、車内アナウンスでは古市で後ろ2両を切り離し、後ろ2両が橿原神宮前に行くとのこと。幸い、後ろから2両目に乗っていたのでそのまま乗っていくことができた。
当麻寺につくと、出口へは、構内踏切を渡っていく経路になっていた。田舎の駅ではよくあるパターンだが、近鉄南大阪線の駅であるとはちょっと意外。しかも、反対向きの列車もやってきて踏切で結構待たされた。急ぎたいところだが、トイレに寄っていくことにした。前日、トイレ(大きい方)に行ってなかったのがちょっと気になってて、レース中にもよおしたら嫌だなと思っていたら、電車の中でもよおしてきたので、ここで済ましておくことにした。駅の雰囲気とマッチした和式のトイレだった。和式のトイレを使うのはいつ以来だろうか?
無事にトイレを済ましてスタート地点に向かう。道は田畑の中を通っていくのどかな道。経路の取り方が何通りもあるので、参加者と思しき人が何人か歩いているが結構みんなばらばらのルートを歩いている。
スタート地点について受付に行くと、参加費を払ってないと言われ、頭が真っ白に。なんせ、レースを走るだけなので現金はほとんど持っていなかったので、ここで払えと言われても払えない。自分では払ったつもりだったのですが。結局、帰ってからもう一度確認するということでその場は許してもらった。帰ってから確認するとやっぱり払っていないことが分かった。いやー、本当に申し訳ない。それにしても、参加費未払いでも参加を認めてくれるとは、ありがたいことです。
トイレに行ったり、受付でひと悶着あったりして、ただでさえ時間に余裕がないのに、ますます余裕がなくなってしまった。スタートまで時間がせまり、開会式も始まる中、急いで準備をする。時間に余裕がないのはやっぱりよろしくない、もうひとつ前の電車で来るべきだったと反省。

いよいよレース

レースは8時30分から2回に分けてスタート。自分は遅い方の8時35分。どうせゆっくり走るので遅い方のスタートでないとみんなに迷惑が掛かってしまう。
スタートからしばらくは緩やかなのぼり、祐泉寺の脇を抜けて、二上山の雌岳に登っていく。登りがそれほど急でないからか、隊列がほとんどバラけず一列になって登っていく。二上山を降りて竹内峠が最初のエイド。ここまで3.5kmで何とも早すぎるエイド地点だが、次が15km地点なので、ここで水を一杯飲んでおく。
第一エイドを過ぎると細かいアップダウンが続きながら登り基調が続く。まだ比較的緩いので隊列が伸びたまま。平石峠を過ぎたあたりから勾配が急になってくる。

葛城山は登りも下りも大変

10kmを過ぎたあたりから本格的に葛城山へののぼりが始まる。高低図で見るとそれまでの勾配と特に変化がないように見えるが、実際に行ってみるとかなり急で、しかも木段が延々と続く。ここまでくるとさすがに結構ばらけてランナーがまばらになる。しかし、今回は遅くても譲らない人が何人かいて、他の大会よりもマナーが悪いと感じた。弘法トレイルでは全くそういうことはなかったし、六甲縦走でもたいていは譲ってくれたが、狭い登山道の真ん中をゆっくり登られるのは他のランナーに迷惑だと思うのだが、初心者向けの大会ではないはずなのになんかマナーの悪い人がいてちょっと気分が悪い。
13.5kmくらいで葛城山の山頂下に出た。葛城山は奈良側からロープウェイもあり登る人も多く、多くのハイカーでにぎわっていた。しかも、食堂や売店もあって飲み物の自販機もある。一人でダイトレに来てたら、ここで食事タイム(ちょっと早いけど)という計画にしそう。ここでおにぎりを一つ食べる。エイドは近いが、石舞台100の時に、もうすぐエイドと思って頑張って、血糖値が下がってハンガーノックになりかけたので、それ以来、エイドと関係なく食べ物を取るタイミングを重視するようにしている。
葛城山は(次の金剛山もだったが)山頂に寄らずに、ここから南の方へ下山する。ここの下りは高低図で見ても急勾配で、本当に急なくだりが延々と続くし、木段の段が一段一段高くてそれを降りるたびに膝にドシッと衝撃がかかる。弘法トレイルで膝を痛めているので、ここは急がずにゆっくりと降りる。
急こう配を降りて国道に出たところが水越峠で、第二エイド。ここで、ソフトフラスクに水を補充。今回はフラスクは2本持ってきて、スタート時には合計1L持って来ていたが、ここまでで1本を飲み切っただけだった。暑い割には水の消費が少ない。だが、実際にはかなり水分を消費していたみたいで、家に帰ってから麦茶を何倍もがぶ飲みしてしまった。つまりレース中の補給が足りていないということであまりいいことではない。
ここではバナナと塩羊羹をいただく。次のエイドがおよそ6km先なので、ここは簡単に補給を済ませて先を急ぐ。

金剛山もきつかった

水越峠を過ぎると今度は金剛山へののぼりが始まる。はじめのうちは、緩やかな林道を上る。ゆったりと登っていくので、葛城山の下りで結構痛めつけられた太ももが少し回復してきた。このままこれくらいの勾配で登れればいいのだが、流石にそうは問屋が卸さない。次第に勾配が急になってきて、やっぱり延々と続く木段が現れる。前半隊列で進んだせいで、自分のペースで行けず、少しオーバーペースかなと思っていたが、案の定この辺でかなりきつくなってきた。金剛山を上ってしまえばあとは基本下りなのでとにかく、ゆっくりでいいから止まらずに進む。止まってしまうと時間は進むが位置は進まないので全然ゴールが近づかないので、きつくなってもとにかくゆっくりでいいから進むことにしている。約5kmののぼりを上り切りようやく、金剛山下の一の鳥居迄やって来た。
そこからしばらく下った、府民の森ちはや園地が第三エイド。
ここでもフラスクに水分を補給。最近は水にクエン酸と塩(ぬちまーすという沖縄の塩)を入れて飲むようにしている。味のない水だけだと飲みにくいし吸収も悪い。かといってスポーツドリンクにすると、甘みが口に残って気持ちが悪い。クエン酸だと後味がさっぱりしていて甘みが口に残ることがないので飲んだ後がすっきりしている。それに塩を適量入れることで浸透圧を適当に高めて吸収しやすいようにしている。ぬちまーすは塩分(塩化ナトリウム)以外に海水中のミネラルがそのまま入っているので、発汗で失われたミネラルを補給するのには最適。
問題は、そのクエン酸と塩を入れて持ってくる容器。今回は3種類の容器を試した。1つ目はダイソーで売っていたしょうゆなどをお弁当に入れる容器。これは小さくて良いのだが口が小さすぎて、入れるのも一苦労だったが出すのも大変で使い物にならないことが判明。2つ目はセリアで売っていたこちらもしょうゆなどを入れる容器。こっちはダイソーのものよりも少しおおぶりで口もやや大きいので、割と入れやすくて出す方も問題なかった。ただ、ちょっとかさばる。3つ目はキャンドゥで売っていた、化粧用のクリームを入れる容器。これはコンパクトで口も大きいが、開け閉めするときにパチッとひっかけるように閉める。中身が漏れることはないがあける時に失敗すると中身がこぼれそうでやりにくい。今回は2つめのセリアの容器がまだましだったが、どうするのが良いかもうちょっと検討が必要だと思った。
第三エイドからゴールまでは約11km。次のエイドまでが5km。ここまで来れば何とかゴールが見えてくる。ここで、残りのおにぎり1つを食べようと思ったが、どうも食欲が湧かないので、チョコ羊羹を食べた。しかし、これが大失敗。チョコの油が胃にもたれて気持ちが悪くなった。トレイルランの時は大会でも、一人で走るときも結構このチョコ羊羹を愛用していたが、後半になって体が疲れた状態で食べるのには向いていないようだ。その時の状態に合わせた補給をするべきだと改めて思った。

最後のひと踏ん張り

第三エイドからは基本は下り基調。とはいえ、細かいアップダウンは続く。20kmを過ぎているのでだいたいいつものことだが、登りはほとんど走れない。下りやフラットなところで走り、登りは歩くといういつものパターンで進む。それでも大きな登りはないので案外順調に進んで、最後の行者杉のエイドに着いた。ここでもフラスクいっぱいに水を補給。チョコ羊羹が胃にもたれているせいで、味のある水分よりもただの水の方が飲みやすい。しかも、たくさん上って来たからかもしれないが、水分の消費量も前半よりも多めになってきている。第四エイドでは最後の頑張り用にエナジージェルを補給。また、エイドで塩羊羹と塩ラムネをもらった。塩羊羹はチョコ羊羹よりも全然食べやすい。また、塩ラムネが案外よかった。今後は補給にちょっと検討しようかと思う。
第四エイドを過ぎても、コースの感じはあまり変わらず、細かなアップダウンが続きながら下り基調で進む。紀見峠の手前がまた、急なくだりで延々と木段が続いていい加減に足が悲鳴を上げだした。ちょうどおしっこがしたくなってきたところだったので、紀見峠のトイレに行って小休止。ここまで来ればあと2kmほどなので、完走は間違いない。
紀見峠からゴールまでも舗装された道だが、勾配が結構急で足が本当に痛い。舗装されている分なお足にやさしくない。結局7時間ちょっとかかってゴール。コースは思ったよりもきつかった。また、季節外れの暑さも結構体にこたえたのかもしれない。もう少し早くゴール出来るかと思ったが意外に時間がかかった。しかも今までにないぐらい疲れた。弘法トレイルの方が距離は長いが、あの時は膝を痛めて最後はずっと歩いていたからそれほど疲労感を感じなかった。今回はずっと走ったので疲れ方も大きかったようだ。
いずれにしても、とりあえず完走できたので、ITRAポイントが2点もらえることになった。これで、再来年に計画しているUTMFのKAIへの参加資格を確保できたので、あとはポイントを気にせず走ることができる。
ゴール地点では、カレーと飲料水が販売されていた。これも初めてのことで、たいていゴールでもなにがしかの食べ物をタダで食べれるのが普通だと思ったが、売っているのは初めてだった。まあ、あまりお腹もすいていなかったし、パンもたっぷり持っていたので、着替えてそのまま帰ることにした。

帰りにちょっとしたトラブル

帰りは、南海高野線紀見峠駅から難波行きの急行で帰った。楽に帰るなら天下茶屋で地下鉄に乗り換えると、始発で座っていけるので楽でよいのだが、思ったよりもゴールが遅く、新今宮から環状線で帰ることにした。ところが、環状線に乗っていると弁天町で列車がストップ。車内アナウンスで森之宮で人身事故があったとのこと。これではすぐには動かないと思い、地下鉄に乗り換え、梅田に出てJR京都線で帰った。こんなことなら、天下茶屋で乗り換えればよかった。まあ、結果論ですけど。環状線大正駅が京セラドームの最寄り駅、ちょうどこの日は日本シリーズの第6戦が開催される日。なので、反対向きの列車に乗っていたと思われる阪神のレプリカユニホームを着た人たちも駅を降りて地下鉄に乗り換えていた。残念ながら阪神は負けてしまいましたが。

COROSのデータが飛ぶ

家に帰ってGPSのデータをチェックすると葛城山の周辺でデータが飛んでいる。

葛城山周辺でCOROSのログが飛んでいる

青は、主催者から提供されたコース。緑がCOROSのデータ。赤がスマホで記録したログ。
GarminのINSTINCTでも結構あったけど、時計が古くGPSの精度が低いからだと思っていたが、新しくてGPS以外にもみちびきやglonassなどマルチGPSかつマルチバンドのCOROSでデータが飛ぶとは思わなかった。せっかく新しく買ったのにとても残念。
また、スマホでとったログと比べると金剛山の近くでもちょっとずれているところがある。

まあ、でも地図があってナビゲーションしてくれるのはありがたい機能で、今回は地図画面はあまり見なかった(というか老眼であまり見ても役に立たないし)が、次のエイドまで何kmかという表示出るのでそれが励みになってこの機能はとてもよかった。まあ、SUNNTOやGARMINでも地図のあるGPSウォッチならある機能なんですけど。気になるのは、GARMINやSUNNTOならこういったデータ飛びはないのかというところ。もう買い替えるかどうかはわからないけど、もし買い替えることがあればまた考えないといけない。
COROSのデータは途中で飛んでいたので、下の地図のルートはスマホでとったものです。

第28回京京立大会に参加してきました。

さて、しばらく間が開いてしまいましたが、今回はオリエンテーリングの話です。
先日10/8に京京立(京大京立命館)の大会に参加してきました。
オリエンテーリングも久しぶりです。前回は、6月のOLP兵庫の大会でした。
alasixosaka.hatenablog.com
本当は、夏の中高選手権にエントリーしていたんですが、夏休みにコロナにかかってしまい、全然回復してなくて、とても走りに行く気分じゃなかったのでパスしました。
alasixosaka.hatenablog.com

なので、5か月ぶりの大会参加です。それにしても、コロナの影響は結構長引きました。まあ、年のせいというのもあるのでしょうが、1か月くらいは咳が続き、おまけに微熱とまではいかないまでも、微妙に熱があって(平熱の0.2-0.3℃増し位)、なんか夜に寝付けないという状況が続いて、体力的にも結構疲弊してしまいました。なので、トレーニングはあまり積めてない状況ではありましたが、オリエンテーリングなら距離的には問題ないのでそれほど影響はなかったと思います。
また、本当は10月は山に入らないと決めているので、オリエンテーリングだろうが、トレイルランニングだろうが参加しないことにしているのです。何故なら、前にも書いたと思いますが、スズメバチに2度刺されていて、次に刺されると本当に危ないと医者から言われているので。蜂が活発に活動するこの時期は山での活動を自粛しているのです。ですので、もちろん、登山もしないし、トレーニングでトレイルを走るということもしません。もっぱらロードを走るか、自転車に乗るかというのがこの時期の過ごし方です。そういえば、先週末のトレイルランニングの大会で大勢がスズメバチに刺されたというニュースがありました。
www.yomiuri.co.jp
本当にスズメバチは恐ろしいです。刺されたらどんなひどい目に合うか、刺された人しかわかりません。特に1匹でなく、大群に襲われたらもうどうしようもありません。とにかく急いで蜂が襲ってこないところまで逃げるしかないですが、その間に何度も刺されてしまいます。それでも1度目は、めちゃめちゃ痛かったし、少し気分が悪いくらいで済みましたが、2度目に刺されたときはアナフィラキシーを起こして、このまま死ぬんじゃないかと思いました。今思い出してもぞっとします。
それでも参加をすることにしたのは、今回は後輩たちが開催する大会ということで、特別に参加することにしました。虫よけもしっかり持って行ったのですが、スタート前に虫よけをかけるのを忘れて、何のためにもっていったのかということになってしまいました。レース前はいつものルーティンがあって、それ以外のことをすっかり忘れてしまうのですね。いけない、いけない。最近は年なので特にそうなり勝ち。
とはいえ、プログラムには蜂がいるとしっかり書かれていますし、熊も出るとも書かれていて、嫌な予感しかしませんでした。また、スタート地点が2か所に分かれていて、第二地区にはハチの巣があるとのこと。幸い、自分のエントリーしたクラスは第一地区だったので問題なかったですが、第二地区だったら本当に嫌だった。
その、虫に関しては、急に気温が下がったからなのか、お天気が今一だったのためなのか、レース中もほとんど遭遇することなく、まるで冬に山に入っているかと思うくらい何もいませんでした。スタート地点に小さなミツバチがいたのと、やぶ蚊が何匹か寄ってきたくらいでしたね。通常今くらいの時期なら、レース中でもちょっとスピードを落とすと場所によってはブンブンとうるさい虫が付きまとったりするものですが、そんなことは一切なく静かにレースを走ることができました。
その点は良かったのですが、肝心のレースの方は、ブランクが開いたせいなのか、それともやはり体調が今一だったのか、逆走を2回もしてしまって、成績は下位に沈むという不本意なものになってしまいました。
そもそもスタート地点に着いたときに、コンパスを見たら自分の感覚と南北が逆だったので、その時から感じがおかしかったので、あまりいい感じはしてなかったのですが、まさか、レース中に2度も南北を勘違いするとは思ってもみませんでした。南北を間違えるということは初心者の頃はやった記憶がありますが、最近では全く記憶がなく、それも2回もやってしまうとは、いやはや情けない限りです。次回はもうちょっと気を引き締めて、こんなしょうもないミスをしないようにしないと。まあ、今回は蜂に刺されず無事に帰ってこれたということで良しとしておこうかと思っています。

Android地図アプリのログをGPXファイルに変換する

<ちょっと間が開いてしまいましたが、自作のAndroidアプリにログ機能を搭載したという話を書きました。
alasixosaka.hatenablog.com
ところが、そのログは、緯度、経度、高度、時刻を順番にカンマ(,)で区切ったテキストをだらだらと書き出しただけのものなので、辿ったルートを地図上に表示させようとするとGPXなどの形式に変換する必要があります。
今回は、その変換用のプログラムの話です。
変換はPC上で行い、プログラムはPythonで記述しました。

GPX形式とは

まず、GPX形式に変換するといっても、それがどんな形式わからないと変換のしようもありません。
といっても、ほぼ素人の自分がここであれこれ解説するよりも、詳しく説明されているサイトを見てもらったほうが早いので、詳しくは下記のリンクを見てください。
tancro.e-central.tv
www.kunimiyasoft.com

要するに、XML形式の一種で、位置情報に特化したファイル形式ということが言えます。ですので、一つの区切りが”<”で始まり、”/>”で終わる形式になっています。
例えば、あるポイントは、

<trkpt lat="36.41274926854364" lon="139.42386455894342"><ele>310</ele><time>2010-09-18T23:14:56Z</time></trkpt>

のように記述されています。lat=の後が緯度、lon=の後が経度、eleで囲まれている部分が高度、timeで囲まれている部分が時刻です。
ですので、カンマで区切られた緯度、経度、高度、時刻を順番にこの形式で出力してやれば基本的にはGPXファイルに変換できることになります。
それ以外には、ヘッダーとか細かい部分がありますが、それらは、上の2つのリンクで見てもわかるように、適当に別のGPXファイルを見てコピペしても問題ないようです。
ただし、問題が一つあります。

高度は変換が必要

アプリの開発のところでも書きましたが、Android端末から得られるGPSの高度というのは、準拠楕円体高というもので、実際の標高とは少し違うものということです。これを実標高であるジオイド高に変換する必要があります。
楕円体高、ジオイド高についての詳細は下記のサイトを見てください。
blog.geolonia.com
ja.wikipedia.org
私はあんまりよく理解していません。とにかく何か変換が必要というくらいの認識です。
そして、日本で標高を計算するには、国土地理院が提供しているデータを用いる必要があるとのことです。
qiita.com
どんな計算をしているのかを理解し、自分でプログラムを組むのは結構大変そうですが、幸いなことにPythonでプログラムを作って公開されている方がおられますので、変換の計算はこのプログラムを利用させていただくことにしました。
github.com
上記のサイトのREAD.MEにも書いてありますが、国土地理院のデータはあらかじめ各自でダウンロードしておく必要があります。
fgd.gsi.go.jp
最新のデータはverが2_2になっています。参考サイトのプログラムはver2_1を使用していますのでその部分も書き換える必要があります。最初エラーになってしばらく何のことかわかりませんでした。

プログラムの詳細

定義など

まず、定数、グローバル変数を定義します。なお、importは省略しています。

GSI_GEOID_FILE_NAME = 'gsigeo2011_ver2_2.asc'

# ジオイドデータはglobalで使う
global geoid
global geoidparam
global glamn
global glomn
global dgla
global dglo
global nla
global nlo

ここはオリジナルのプログラムとほぼ同じですが、ジオイドのファイル名が上にも書いたようにダウンロードしたバージョンが2_2でしたのでそれに合わせてあります。

ジオイドデータ読み込み

ここはオリジナルと全く同じです。

# ジオイドデータを読込みます。事前作成したデータファイルモジュール(geoidData.py)が存在しない時は
# 国土地理院のジオイドデータファイルから作成します。(次回以降の処理が若干早くなる)
def getGeoidData():
    global geoid
    global glamn
    global glomn
    global dgla
    global dglo
    global nla
    global nlo
    if (os.path.exists(os.path.join(os.getcwd(), 'geoidData.py')) == False):
        print('国土地理院のジオイドファイルからデータファイルを作成します(初回のみ)')
        if (os.path.exists(os.path.join(os.getcwd(), GSI_GEOID_FILE_NAME)) == False):
            print('エラー:データファイルがありません')
            print('同じフォルダに国土地理院のジオイドファイル「' + GSI_GEOID_FILE_NAME + '」を置いてください')
            return False
        createGeoidData()
    else:
        print('データファイルを読込中')
        module = importlib.import_module('geoidData')
        geoid = module.setGeoid()
        misc = module.setMiscData()
        glamn = misc["glamn"]
        glomn = misc["glomn"]
        dgla = misc["dgla"]
        dglo = misc["dglo"]
        nla = misc["nla"]
        nlo = misc["nlo"]
    # ヘッダのdglaの有効桁数違いで地理院プログラムと微小な差異があったので、より計算値に近づける
    dgla = math.floor(dgla * (nla - 1)) / (nla - 1)
    dglo = math.floor(dglo * (nlo - 1)) / (nlo - 1)
ジオイドデータの書き出し

ここもオリジナルそのままです。初回だけこの関数が実行され、実行されると、geoidData.pyというファイルができます。

# 国土地理院のジオイドデータからPythonのデータを書きだして、次回から使えるようにします。
def createGeoidData():
    global geoid
    global glamn
    global glomn
    global dgla
    global dglo
    global nla
    global nlo
    f = open(os.path.join(os.getcwd(), GSI_GEOID_FILE_NAME), 'r')
    # f = open('gsigeo2011_ver2_1.asc', 'r')
    line = f.readline()
    #  20.00000 120.00000 0.016667 0.025000 1801 1201 1 ver2.1
    linestr = '' + line
    linestr = linestr.strip()
    header = linestr.split(" ")
    glamn = float(header[0])
    glomn = float(header[1])
    dgla = float(header[2])
    dglo = float(header[3])
    nla = int(header[4])
    nlo = int(header[5])

    geoid = {}
    la = 0
    lo = 0
    while line:
        line = f.readline()
        linestr = '' + line
        linestr = linestr.strip()
        g = linestr.split(" ")
        glen = len(g)
        for i in range(0, glen):
            if (g[i].strip() == ""):
                continue
            if (g[i] != '999.0000'):
                geoid[str(la) + "_" + str(lo)] = float(g[i])
            lo += 1
            if (lo == nlo):
                lo = 0
                la += 1

    f.close()

    fw = open('geoidData.py', 'w')  # 書き込みモードで開く
    fw.writelines('def setGeoid():\n')
    # geoid = [[999] * 1201 for i in range(1801)]
    fw.writelines('\tgeoid = {}\n')
    for la in range(0, nla):
        for lo in range(0, nlo):
            if (str(la) + "_" + str(lo) in geoid.keys()):
                fw.writelines(
                    '\tgeoid["' + str(la) + '_' + str(lo) + '"] = ' + str(geoid[str(la) + "_" + str(lo)]) + '\n')
    fw.writelines('\treturn geoid\n')
    fw.writelines('def setMiscData():\n')
    fw.writelines('\tmisc = {}\n')
    fw.writelines('\tmisc["glamn"] = ' + str(glamn) + '\n')
    fw.writelines('\tmisc["glomn"] = ' + str(glomn) + '\n')
    fw.writelines('\tmisc["dgla"] = ' + str(dgla) + '\n')
    fw.writelines('\tmisc["dglo"] = ' + str(dglo) + '\n')
    fw.writelines('\tmisc["nla"] = ' + str(nla) + '\n')
    fw.writelines('\tmisc["nlo"] = ' + str(nlo) + '\n')
    fw.writelines('\treturn misc\n')

    fw.close()
ジオイド高の計算

ここが、計算の主体となる部分です。ここもオリジナルそのままです。
緯度、経度を引数にして関数を呼び出すと、ジオイド高を返すというルーチンです。

# 緯度経度からジオイド値を求める
def getGeoidValue(lat, lon):
    global geoid
    global glamn
    global glomn
    global dgla
    global dglo
    global nla
    global nlo
    # 囲う矩形を求める
    j = int(math.floor((lon - glomn) / dglo))
    i = int(math.floor((lat - glamn) / dgla))
    if (i < 0 or i >= nla or j < 0 or j >= nlo):
        # print('エラー:緯度経度が範囲外です')
        return 999.00

    if ((not (str(i) + "_" + str(j) in geoid.keys())) or (not (str(i) + "_" + str(j + 1) in geoid.keys())) or (
    not (str(i + 1) + "_" + str(j) in geoid.keys())) or (not (str(i + 1) + "_" + str(j + 1) in geoid.keys()))):
        return 999.00
    wlon = glomn + j * dglo
    elon = glomn + (j + 1) * dglo
    slat = glamn + i * dgla
    nlat = glamn + (i + 1) * dgla

    t = (lat - slat) / (nlat - slat)
    u = (lon - wlon) / (elon - wlon)

    Z = (1 - t) * (1 - u) * geoid[str(i) + "_" + str(j)] + (1 - t) * u * geoid[str(i) + "_" + str(j + 1)] + t * (
                1 - u) * geoid[str(i + 1) + "_" + str(j)] + t * u * geoid[str(i + 1) + "_" + str(j + 1)]
    Z = Z * 100000
    Z = math.floor(Z + 0.5)
    Z = Z / 100000
    return Z
高度を変換してGPXファイルに書き出す。

元のプログラムはCSVファイルを書き出すようになっていましたが、ここをGPXファイルを書き出すように変更しました。
関数名はconvertCSVとそのままですが。
まず、ログファイルを読み込みます。読み込んだ値はdataという配列に格納されます。配列は1次元配列で、緯度、経度、高度、時刻の順に並んでいます。
ですので、配列要素の4の倍数の時が緯度、4の倍数+1の時が経度、4の倍数+2の時が高度、4の倍数+3の時が時刻となっています。
GPSデータの緯度はlat、経度はlan、楕円体高はEllapsoidHeightという変数に格納されます。そこで緯度、経度からジオイド高を読みだして、楕円体高-ジオイド高より標高を計算してelavationという変数に格納します。
そうしたら、 with open(outfile, "w") as f: 以下で、GPXファイルとして書き出しています。最初の部分は上に書いたおまじないで、適当なほかのGPXファイルからとってきた部分をコピペして書き出しています。
そのあとは、GPXファイルの形式に従って、緯度、経度、高度、時刻を書き出すようになっています。

# CSVファイル中の高さ(楕円体高)カラムを標高値に置き換えて出力します
def convertCSV(infile, outfile):
    global geoid
    # header check
    
    hasInvalid = False
    with open(infile) as f:
        lines = f.readlines()
    for s in lines:
        data = s.split(",")
    length = len(data)//4
    for row in range(length):
            temp = data[row*4+2]
            EllapsoidHeight = float(data[row*4+2].replace(',', ''))
            lat = float(data[row*4].replace(',', ''))
            lon = float(data[row*4+1].replace(',', ''))
            geoidval = getGeoidValue(lat, lon)
            elevation = EllapsoidHeight - geoidval  # 標高=楕円体高-ジオイド高
            if (geoidval == 999.00):
                # ジオイド値が正常に取得できない
                elevation = 999999
                hasInvalid = True
            data[row*4+2] = "{0:.3f}".format(elevation)  # 高さカラムを差し替え
            #fout.writelines(delimChar.join(row) + '\n')  # 出力
            print(data[row*4+2])
    if (hasInvalid):
        print('ジオイドの取得に失敗したデータがあります。確認ください。該当するデータは標高を 999999 にしています。')
    else:
        print('処理を正常に終了しました')
    with open(outfile, "w") as f:
        f.write('<?xml version="1.0" encoding="utf-8" standalone="no"?>"\n')
        f.write('<gpx xmlns="http://www.topografix.com/GPX/1/0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" '
                'xsi:schemaLocation="http://www.topografix.com/GPX/1/0 http://www.topografix.com/GPX/1/0/gpx.xsd" '
                'creator="Bike Route Toaster http://bikeroutetoaster.com/" version="1.0">\n')
        f.write('<trk>\n')
        f.write('<Name>'+infile+'</Name>\n')
        f.write('<trkseg>\n')
        leng = len(data)//4
        for i in range(leng):
            Time = convertTime(data[i*4+3])
            f.write('<trkpt lat="'+data[i*4]+'" lon="'+data[i*4+1]+'">\n')
            f.write('<ele>'+data[i*4+2]+'</ele>\n')
            f.write('<time>'+Time+'</time>\n')
            f.write('</trkpt>\n')
        f.write('</trkseg>\n')
        f.write('</trk>\n')
        f.write('</gpx>\n')
時刻の変換

時刻については、元のログファイルとGPXファイルの形式が異なるので、これも変換が必要です。
convertTimeという変数を使って変換しています。ログファイルの時刻は、年が4桁、月、日がそれぞれ2桁、そして、時、分、秒もそれぞれ2桁の合計14桁の整数として表現されています。
そこで、元の数値を10の10乗で割った4桁が年、そして残りの数字を10の8乗で割った値が月、というように順番に割り算を繰り返し、時刻を求めてそれらをGPX形式に合わせた文字列として返しています。

def convertTime(timeSS):
    time = float(timeSS)
    year = time//1e10
    yearS = str(year)
    time = time - year*1e10
    month = time//1e8
    monthS = str(month)
    time = time - month*1e8
    day = time//1e6
    dayS = str(day)
    time = time - day*1e6
    hour = time//1e4
    hourS = str(hour)
    time = time - hour*1e4
    minute = time//100
    minuteS = str(minute)
    sec = time - minute*100
    secS = str(sec)
    timeS = yearS+"-"+monthS+"-"+dayS+"T"+hourS+":"+minuteS+":"+secS+"Z"
    return timeS
メインルーチン

最後がメインルーチンです。
ファイルダイアログを表示して、変換するログファイルを指定し、出力ファイルはファイル名をそのままにして、拡張子を".log"から".gpx"に変換したファイルにしています。
getGeoidDataは元のプログラムのままで、ジオイドデータを一度だけ変換するものです。convertCSVが変換の主体で、ジオイド高から実際の標高を求めてGPXファイルの出力します。

# main
def main():
    #args = sys.argv

    # 引数とファイルの存在チェック
    
    root = tk.Tk()
    root.withdraw()
    typ = [("ログファイル",".log")]
    dir = 'h:\gps\logfile'
    filein = filedialog.askopenfilename(filetypes=typ, initialdir=dir)
    fileout = filein.replace(".log","raw.gpx",1)
    # ジオイド情報の取得
    getGeoidData()
    # CSVファイルの高さ情報からジオイド高を引いたものを複製する
    convertCSV(filein, fileout)


# 実行
main()

実行結果

実際に自転車で近所を走った時のデータを変換してみました。
軌跡については下の図のように問題ありませんでした。

軌跡をカシミール3Dで描画した図

ところが標高については、下のグラフのように変な値になっています。
ところどころでフラットな部分があって、正しく標高が取得できていないようです。GPSの精度の問題でしょうか? 変換前のグラフと変換後のグラフを比べると20-30mほど標高に差があり、変換自体はうまくできているようです。

上が変換前、下が変換後のグラフ。ところどころ正しく標高が取れていない。

yamapのアプリなどでは、あとで標高を見てもこんなに変なことになっていないので、理由はよくわかりません。まあ、最悪軌跡(緯度と経度)がちゃんととれていれば、標高は後から計算することができるので何とかなるので、一応これで良しとしようかと思います。
下のグラフはカシミール3Dで標高を直した図です。こんな感じでできるのでちょっと手間がかかりますが、そもそもがバックアップが目的なので、まあこれでいいのでは、と思っています。

カシミール3Dで正しい標高に直した図

ちなみにCOROSのデータはこんな感じです。COROSの方がデータが滑らかな感じがしますけど。ほぼほぼあっている感じですね。

COROSの標高データ

ちなみに、軌跡のスタート地点はここです。北摂ラルプデュエズということで最近ちょっと話題のポイントです。ちゃんとグーグルマップにも載っています。道路の先に見えるのは最近完成した安威川ダムです。
本物のラルプデュエズには比べるまでもないですが、それでもここを一気に登るのは結構きつく、いい練習になります。

北摂ラルプデュエズ

本当は走り始めがここというわけではなく、自宅からここまで自転車でやってきて、やっとアプリのことを思い出して、ここからスタートしたというのが実際のところです。この日は、忍頂寺まで行って帰ってきました。まだ、コロナから完全に回復せず、咳は出るは、体は思いはで、どうも今一です。

新型コロナ発症

やあ、ついにかかってしまいました。
先週、比叡山に走りに行った日、子供が模試から帰ってきて調子が悪いと言い出し、その日は風邪ぐらいに考えて、風邪薬を飲ませて休ませた。翌日になるとさらに悪化し、熱が38℃になり、新型コロナを疑って簡易検査キットで検査。ところが結果は陰性だったので、やっぱり夏風邪かと思い、もう少し強めの、病院からもらって余ってた風邪薬を飲ませた。だが、翌日にはさらに熱が高くなり、再度検査すると陽性に。
折しも、お盆休みの真っ最中、しかも台風直撃の日。どうしようかと困ったが、幸い近所でやっている病院が1件だけあったので、そこを受診して、薬をもらって来た。
ところが、今度は自分がそのころから喉が痛くなり始め、翌日には発熱。検査すると陽性になって、自分もコロナにかかったことが判明。子供の行った病院と同じ病院を受診した。そして、その翌日には家内も調子を崩し、結局家族全員コロナに感染してしまった。
インフルエンザや風邪の時は子供の看病をしていてもかかったことはなかったのに、コロナだと簡単にうつってしまう。いやー本当に感染力が高いんだということを実感した。5類になってなめていたわけではないが、多少の気のゆるみもあったのかもしれない。ワクチンも昨年の4月に受けたのが最後で受けていなかったし。こんなことならワクチン受けときゃ良かったと後悔しても後の祭り。
何が一番つらいって、人によって症状は違うのだろうけど、38℃以上の熱がずーっと続くこと。自分の場合3日くらい解熱剤を飲んでも熱が続いて本当につらかった。ワクチン打っても副作用で熱が出てつらいが、大体1日で収まるし熱以外の症状は皆無、ところがコロナになってしまうと、頭は重いし、喉は痛いし、鼻水は出るしで、風邪の症状オンパレード。なので、ワクチンの方が何倍も楽だということを改めて実感した。今日の時点で大体回復してきたので、まあ、喘息持ちで、高齢者の自分としては重症化しなかっただけましだったと思うしかない。しばらくたったらちゃんとワクチン接種を受けようと改めて思う今日この頃です。
療養中に、しんどくて、寝てるかゴロゴロするしかなかったが、モチベーションを保つために、比叡山のトレイルランの試走の動画を見たり、NHKのグレートレースで先日放送のあったUTMFの放送を録画してまだ見てなかったのでそれを見たりして、やる気を維持していた。グレートレースの方はトップランナーたちの走りを見ても自分とはあまりに違う世界なのであまり参考にはならなかったが。それでも、再来年にはKAIに参加したいと思っているので、ちょっとは参考になったのかな。ついでにKAIの参加資格についても調べてみた。KAIの場合は、エントリー期間(だいたい、レースの前年の10月中旬から下旬ごろ)の直近2年のITRAポイントの合計が3ポイント以上で、内最低1レースは直近1年以内でのポイントであることが条件になっている。現時点で自分の場合、23年1月の石舞台100で2ポイント、4月のモンキートレイルで2ポイント持っているので、来年の大会にエントリーするなら参加資格を満たしているということになる。でも、一応挑戦は再来年にしようかと考えていて、来年はKAIの行われるほぼ同じ時期に比叡山国際トレイルランニングの50㎞に挑戦する予定。なので、まあ、そこで完走できれば参加資格的には問題ないが、比叡山は50㎞だとウェーブ2に入ってしまうと最終関門の時間が19時でウェーブ1と共通に設定されていて、結構頑張らないとそこでリタイアということになりそう。特に雨などで天候が悪かった場合、完走できないことも想定されるので、初めから計算に入れておくのは望ましくない。ということで、今年の10月以降で保険に最低1ポイントITRAポイントを取らないとだめだということが分かった。
比叡山の方は、Youtubeの動画で、こちらの動画が非常に参考になった。
www.youtube.com
50kmの全コースを試走しながら、ポイントポイントをちゃんと解説してくれている。自分はまだ、半分くらいしか試走ができていないが、そこでつかんだ自分で思ったポイントと動画で言っているポイントがほぼ同じなのでやっぱりそうなんだということをあらためて思った。特に、横高山への登りが最大のポイントという点はやっぱりそうかという感じ。また、上位を狙う人はここは走りましょうとか、完走を目指す人は歩いてもとかレベルによってちゃんと説明している点も非常に良かった。それにしても、カメラ片手にしゃべりながら試走して7時間切りとはめちゃめちゃ早い。まあ、50マイル完走するにはこれくらい走れないとだめなんでしょうけど。ちょっと世界が違うなあと思った。
結局最後はトレランの話ばっかりなってしまったけど、まあ、トレランのブログなんでこんなもんでしょう。

再び比叡山へ

2週間前に、比叡山国際トレイルランニングのコースの下見として、延暦寺から坂本まで走ってきました。
alasixosaka.hatenablog.com
本当はその時に、もう一度上り返して、青龍寺から八瀬の方まで降りる予定だったのですが、坂本に降りてきた時点でとても暑くて、そこでリタイア。
今回はその続きで、坂本から仰木峠まで行ってきました。
前回と同様にJRで比叡山坂本駅まで行きました。そこからケーブルの駅を目指して歩き、今回のスタート地点、日吉東照宮を目指します。
前回、飲み物が途中でなくなったのもリタイアの原因だったので、駅前のコンビニで、冷凍のスポーツドリンクを購入。ハイドレーションの1.5Lと合わせて、今回は2L体制で出発です。

スタート地点の日吉東照宮

まずは、東照宮にお参りして本日の無事を祈願。2,3日前に、修学院の方で京都一周トレイルのコース近くで熊に襲われた人がいるとのことで、一人旅なのでちょっとビビりながらスタートです。今回は熊鈴も持参して一応対策をしておきました。
まずは、延暦寺を目指します。まず、比叡山高校のグラウンド脇を通り抜ける。比叡山高校といえば何度も甲子園に出ている名門校。こんなところで練習していたのか。今年は滋賀県代表は近江高校。ということで野球部のメンバーは今日も練習をしていた。途中でマネージャーの人らしき女性とすれ違って、挨拶をされた。前回はイベントがあって、駅からケーブルの駅に向かう途中の辻々に生徒が立ってて挨拶されたが、そういう時でなくとも自然に挨拶できるように普段から指導されているのだろう。とても素晴らしいことだと思った。
ところで、このグラウンドにはトイレがあって、OsmOutdoorではここにトイレマークがついているのだが、さすがにこれは学校のトイレで普通にハイカーが使えるとは思えない。
グラウンド脇を抜けると、予想通りに結構きつい登り。すぐに汗だくになってしまう。途中にもたて山というところを通るのですが、そこにケーブルカーの駅があって、その辺がまだ中間地点くらいと思っていた。

ケーブルカーのもたて山の駅の近く。

そしたら、もたて山の駅を過ぎたらすぐにケーブルカーの延暦寺の駅が見えた。思ったより登りは短いという印象。
ケーブルの駅からは琵琶湖がよく見えた。つまり良いお天気で暑いということ。

琵琶湖の眺め

そこから根本中堂前の広場に向かう。途中に拝観料を支払うところがあるのだが、ハイカーはその旨申し出れば無料で通ることができる。何回か通っていたのだが、今回はトレイルランニングと言ってしまったのがまずかったのか、境内は走るなと注意を受けてしまった。まあ、考えてみれば神聖な場所なのだし、他の参拝者もいるので当然といえば当然なのだろうけど。この日はお3連休の最終日で参拝客も結構いたのでおとなしく注意に従って、東塔の区域を抜けるまでは歩いて通った。もっとも、登りが結構きついので半分くらいは走れと言われても、走れないのだけども。
東塔を抜けるとしばらくトレイルのような道が続き、西塔に到着。ここにも参拝者の方が結構おられるので、中では歩いて通る。

西塔の釈迦堂のところにあるトイレ。コース途中のトイレとしては貴重。

西塔を抜けるといよいよ、本格的トレイルの始まり。ここは京都一周トレイルと東海自然歩道の両方が重なっている部分。そして、その両方のコースはトレイルを進んで玉体杉の方に向かうのだが、比叡山国際のコースは途中から青龍寺に向かって道を左に折れる。ここは、東海自然歩道を二ノ瀬から大津京間で行ったときに間違えて行ってしまったところ。

青龍寺への分岐。

ここから青龍寺までは比較的傾斜の緩い下りで道もよく、タイムの稼ぎどころだと思った。
ただし、青龍寺を過ぎると普通の登山道になって、それほどスピードが出ない。おまけに、今日は誰も通っていないのか蜘蛛の巣だらけ。結構マイナーな登山道なのだろう。
地元のライオンズクラブの方がNoを振った標識を立ててくれていてそれなりに整備はされているのだが。
登山道を八瀬の近くまで降りて、今度は横高山の方へ上り返す。この降りてきたところが前回想定していたゴール地点。まあ、あの暑さではちょっと無謀だったと思うので、前回は坂本でやめて正解だった。
横高山へもライオンズクラブの標識が立っていてNo.が増えていく。標識に励まされるのはよいのだが、ここの登りはかなりきつい。そして相変わらず蜘蛛の巣だらけ。
前回、ここを下山口と決めたときに、降りてから何かあるかと思って、GoogleMapのストリートビューで調べてみたが、登山口というバス停はあるものの、周りにはお店や自販機すらなく何にもない感じだった。
やっぱりここの登山道は結構すたれているのかもしれない。あまり人が来ないということは野生動物が出てくる可能性が高いということで、熊に警戒しながら、鈴を鳴らしながら登る。鈴をつけた位置が悪かったようで、下りや走っているときはなってくれるのだが、登りでは鳴らないので、手で鈴を鳴らしながら進む。途中、動物の足跡らしきものもあって(熊のものかどうかはわからないが、割と大きかった)、こわごわの登山だった。幸い虫とトカゲ以外には出くわすことなく上ることができたが。
標高差で言うと坂本からの登りの方が若干多いのだが、つづら折れで延々と登りが続き心が折れそうになる。しかも本番のコースでは疲労が出始める20㎞過ぎにこの登りがあるのだから相当にきついことが予想される。ここを体力を残して乗り切れるかが大きなポイントになりそう。
標識に励まされてようやく、峠まで登ってきた。

横高山下の峠。ここでコースミス。

ようやく登り切ったと思ってホッとしたのか、やらかしてしまった。本来は、京都一周トレイルのコースに従ってさらに横高山に上らないといけないのに、東海自然歩道の方に行って比叡山ドライブウェイをくぐって、トラバースする道を行ってしまった。しかもここは走りやすいのでどんどん行ってしまって1㎞以上行き過ぎてしまった。今回も、COROSにコースデータを入れておいてコース外れの警告が出るようにしておいたのだが、途中で何度も警告が出るもんで慣れっこになっていて、本当にコースを外れているのにしばらく気が付かなかった。だいぶ行ってから警告の画面を見て、入れておいたルートが全く地図に表示されていないのを見てえっ! てなった。そしてスマホでようく確かめてみると思いっきりコースを外していることが分かって愕然とした。しかも、暑さのせいか、汗で水滴がついているせいかスマホの調子が悪い。ロック画面から解除できず画面が真っ暗になる。仕方ないので、ここでリセットをかける。待っている間、すごいさみしい気分になった。一応比叡山ドライブウェイがすぐそばを通っていて車が時々通るのでまだましだったが、全くの山の中だったらもっとさみしい思いをしただろう。
ここから面倒なのでそのまま東海自然歩道を進んで仰木峠に向かおうかとも思ったが、コースの下見が目的なので戻って本来のルートを行くことにした。
結局さっきの峠まで戻り、そこから横高山に上る。ここの登りも結構きつい。八瀬からの登りもきつかったが、峠まで来てホッとしてまた登り。ここは本当にきつそうだ。ここの峠はエイドステーションになっているので、エイドで一息入れてまた、気合を入れなおして上るという感じになりそう。

横高山山頂。やっと着いたかという感じ

そして、登りは横高山で終わらず、もう一つ水井山というのに上る。横高山ほどきつくないが、ここまででかなり疲労しているはずなので、この登りもきつそうだ。
水井山を登れば後は細かいアップダウンで仰木峠まで走れる。この辺りでくたばって走れないと完走が遠のくことになりそう。
12時過ぎに仰木峠に到着。途中でミスコースしたおかげで予定より少し遅れてしまった。

仰木峠。

比叡山国際のコースはここから稜線を小野山、梶山と辿っていくことになっている。梶山からは大原に降りるルートがあることはあるし、今日はまだ元気があって、梶山まで行こうと思えば行けそうだったが、あまりマイナーなルートはやっぱりちょっと怖いので、仰木峠でやめておくことにした。
ここから東海自然歩道を通って大原の方の降りる。道は途中で2つの分かれていて、そのまま東海自然歩道を行くコースと、左に分岐して京都一周トレイルのコースを行く道。東海自然歩道だと野村別れというバス停の方に出てきて、京都一周トレイルだと戸寺というバス停のところに出る。以前に東海自然歩道を通っているので、京都一周トレイルの道を行こうかとも思ったが、野村別れの方がコンビニやレストランがあって開けてそうだったので、東海自然歩道の道を通る。途中で道が結構ぬかるんでいて通りづらかったところがあった。以前に来たときは冬だったのであまり印象に残っていなかったが春から秋くらいまでは要注意かもしれない。ただ、道幅は結構広くて通りやすい。
降りてきたところで、ひなたに出て、日差しが強くて暑い。それでも下がアスファルトでないだけましなはずだが。それにしても山の中は涼しいと改めて思った。

登山道から降りてきたところ。東海自然歩道は写真の左へ進んで、三千院の方に向かう。

ここで東海自然歩道に別れを告げて、バス道の方へ向かう。バス道に出たところで、角のお蕎麦屋さんに入って昼食にした。ちょうどお昼時でお店は結構混んでいた。

今回も、お昼はお蕎麦でした。

最近、蕎麦づいている。前回も坂本に降りてきたときにお蕎麦を食べたし、実は、2日前にも実家に戻った時に実家の近くのお蕎麦屋さんに行っている。そこのお蕎麦屋さんは前から行きたいと思っていて、いちどチャレンジしたのだけれど、その時は蕎麦が売り切れで食べられなかった。今回は最後の一人でギリギリセーフで、念願のお蕎麦を食べることができた。
坂本のお蕎麦は正当なざるそばという感じだったが、大原のお蕎麦屋さんは出汁が京風で、ちょっと甘いのが特徴だった。でもお蕎麦は腰がしっかりしていておいしかった。
野村別れから京都バスに乗って国際会館まで行って地下鉄、JRと乗り継いで帰った。バスはもっと混んでいるかと思ったが、お昼くらいでまだ観光客が帰るには早い時間だったのかガラガラだった。
さて、これでコースを半分とちょっと試走したことになる。このインターバルで行くと次回は9月ということになるが、そろそろ蜂が嫌な季節になるので、次回は秋が深まってからということになりそう。
暑いのは暑いのでちょっと嫌なのだが、暑さが収まってくると蜂が活発に活動し始めるのでいつも山に入るのは避けている。

比叡山は涼しかった、でも麓は・・・

いやー暑いですね。年々暑さが増しているような気がします。こんなに暑いと、夏場のトレイルランはある意味命がけみたいになってきます。
下の写真は、金曜日に撮ったものですが、外の温度はなんと41.2℃。ちょっと直射日光が当たっていたかもしれませんが、こんな温度は今まで見たことがありません。外を歩いているだけでくらくらしてきます。

外の温度は40℃越え

さて、タイトルの通り、昨日(7/29)に比叡山を走ってきました。
前にも書いたように、来年は比叡山国際トレイルランニング(以下、比叡山)に挑戦しようかと考えています。
alasixosaka.hatenablog.com
その時も書きましたが、比叡山は関門がかなり厳しいので、下見は重要ということで、まずは前半だけ走ってみようと思って行ってきました。
まずは、スタート地点の山頂付近を目指します。行き方は、おおきく3通りあって、叡山電鉄に乗って八瀬からケーブルカーとロープウェイを乗り継いでいく方法、坂本からケーブルカーで行く方法、京都駅からバスで行く方法。バスが楽といえば一番楽なのですが、一番時間がかかるし、朝早い時間のバスがないので、バスは却下。八瀬と坂本では自宅からのアクセスでは坂本との方が行きやすいので、今回は坂本からケーブルカーで行きました。ケーブルの坂本駅は、京阪の坂本駅から歩いて約15分。JRの坂本比叡山駅から歩いて約22分です。バスもあるのですが、早い時間にバスがなかったので、JRの駅から歩いていきました。ケーブル坂本駅のすぐそばに比叡山学園があって、何か行事をやっていたらしく、朝から生徒さんや先生(?)が途中の道のあちこちに立って、「おはようございます!」と言ってくれるので(たぶん行事の参加者と勘違いされている)、こちらも挨拶を返しながら駅に向かった。
9時のケーブルカーに乗ったが、もうすでに麓は暑くなり始めていて、ケーブルカーに座ったら汗がどっと出てきた。しかも冷房がない。しかし、延暦寺の駅に着くとさすがに標高が高いだけあって涼しい。これなら走れるかと思ってスタート地点に向かう。
比叡山のレースの本当のスタートは延暦寺会館の前らしいのだが、今回は根本中堂前の広場からスタートした。

根本中堂前の広場からスタート

ここへは、一度坂本に降りてからまた戻ってくるというルートになっているので、あらかじめルートをGPXファイルで書き出して、自作のアプリに入れておいたが、行って、戻って、また行ってと道が3本交錯しているので、どこに向かったらいいのかわかりづらい。まあ、本番では他の参加者についていくだけなので問題ないのだろうけど。
まずは、比叡山の山頂を目指す。山頂への道で少し迷うがすぐにリカバリー。COROSのAPEX2 Proにもルートを入れておいたので、道を外すとアラームが鳴ってくれる。ただ、地図に関しては、老眼のためほとんど役に立たないのが残念。山頂までの登りは結構きついので、スタート直後にここに来ると結構渋滞しそう。
山頂を過ぎると、少し下って、昔のスキー場の後を横切る。ここからは下り基調になる。途中で京都一周トレイルのコースにつられてコースアウト。コースアウトはCOROSが教えてくれるが、結局どうすればリカバリーできるのかはスマホの地図を見ることになるので少し微妙。まあ、老眼は仕方ないのであきらめるしかない。下っていくと再び京都一周トレイルのコースと合流。ここは京都一周トレイルのコースでよいと思うのだがなぜ別ルートを取っているのだろうか?
しばらく、京都一周トレイルのコースを進む。この辺りは、地面が砂地で滑りやすい。今日はローンピークを履いてきたが、ローンピークは少し滑りやすいのであまり向いていないかもしれない。途中でスマホを見ていたら滑ってこけてしまった。
そして、京都一周トレイルのコースと別れて左手の沢に降りる。沢を超えて登っていくのだが、沢に降りる所が道が崩落していて、わかりづらい。

地図の丸印のあたりが崩落個所
沢へ降りる道が崩落していた。写真は沢を渡ってから振り返ったところ。右手奥からやってくる感じになるが、沢の手前で道がなくなる。

ここから、第一エイドのロテルド比叡までは上り基調になる。細かいアップダウンを繰り返しながら登っていく。傾斜はそれほどきつくないが、アップダウンの繰り返しが微妙に体力を削っていくし、ペースが上がらない。ロテルド比叡の手前は傾斜が思ったよりもきつくつらい。今夏実際にレースを走った人のルートをもとにGPXファイルを作って持ってきたのだが、ロテルド比叡手前のTV局の中継所の手前でなぜか左に折れてトレイルを強引に進む感じになっていた。一応そのルート通りに行こうとしたが、道がわからず迷ってしまった。ようやく踏み後のようなものを見つけてドライブウェイの方に抜けられたが、あとでYamapのルートを見ると、そのままTV局の中継所脇を抜けてドライブウェイに出ている。どう考えてもこっちのルートの方が素直だ。
ドライブウェイに出るとすぐにロテルド比叡がある。この辺りがレースでは第一エイド地点。
しばらくドライブウェイ沿いを進むと車用のゲートがある。ゲートの手前までは左側の歩道を来たが、ゲートの先は右に歩道があって東海自然歩道になっている。

ゲートの先は右の歩道を進む

しばらくドライブウェイと並行して進むのだが、そればっかり考えててドライブウェイに寄りすぎてまたまたコースアウト。
そして、ドライブウェイと別れて、大きな沢沿いをコンタリングしていき無動寺方面に向かう。この辺りは比較的走りやすい。
無動寺の中も微妙にわかりにくくて地図を見ながら進む。
無動寺を抜けると下りにかかる。

下りの途中で琵琶湖が見える

ここの下りは初めはよいが、途中から石ころがゴロゴロしているところや、微妙に崩れた石段のあるところなどが続き、全然スピードが出せない。おまけに水が切れてしまって戦意喪失。今日はハイドレーションに1.5L持ってきていた。普通の気候なら20㎞走っても余るくらいの量だが、12‐3㎞でなくなってしまった。さらに戦意を喪失。おまけに標高は下がるは、お昼過ぎて気温は高くなるわで、どんどん暑くなってくる。
さすがに、坂本からまた比叡山まで登り返す気力がなくなった。今日は坂本に降りてそこでやめようと決断。ケーブル坂本駅の下を通って、JRの駅に向かう道に出て、途中にあったお蕎麦屋さんでそばをいただきました。この辺りはお蕎麦屋さんが何軒かあってそばが名物なのかな? そば以外にもおいしそうなお店がいくつかありました。そのあとコンビニでアイスとスポーツドリンクを買ってJRで帰りました。
今日の反省は、水が足りなかったこと。帰りに寄ったコンビニで凍ったスポドリを買っていくべきでした。
今日の収穫。坂本までの下りは足元が悪く、ローンピークではちょっときつい。モンブランかもっと足回りのしっかりした靴の方が良いと思った。また、レース本番では足首のテーピングも欠かせないと思った。
それ以外には、タイム的には平均で12.5分/㎞といったスピードだった。夏場で暑いことと、コースを何度も間違えているので、本番ではもっとスピードが上がると思ったが、平均で13分/㎞では走らないといけないことを考えるとやっぱり完走するには周到な準備が必要だということを感じた。
今日は予定していたコースを全部走りきることができなかったので、また来月にでも続きを走りに行きたいと思います。でも、この暑さなので距離はこれくらいにしておいたほうが無難ですね。

Android地図アプリにログ機能を搭載する

Androidのアプリで外部ストレージにファイルを書き込む方法について調べて、簡単なアプリを作ってテストをしました。
alasixosaka.hatenablog.com
alasixosaka.hatenablog.com
今回は、いよいよ自作の地図アプリにログを取らせてファイルに保存する機能を搭載します。
方法としては、大きく2通りあり、一つは、配列にログデータをためておいて、活動を終了するときに一気にファイルに書き出す方法(前々回にやった方法)。もう一つは、ログをGPSの位置が更新されるたびにファイルに書き出す方法(前回にやった方法)です。
一長一短があるのでどちらにするか悩ましいところです。前者の方法は、ストレージにアクセスする回数が最後の1回だけなので、SDカードへの負担が減り、SDカードがエラーで死んでしまうというリスクは減らせます。ただし、内部メモリーを沢山使うことになるので、長時間の活動を記録するときにメモリーが足りるかどうかが心配です。後者の方は、メモリーオーバーのリスクはないですが、頻繁にSDカードをアクセスすることになるのでSDカードの寿命が心配です。Androidデベロッパーサイトにも、頻繁なアクセスは推奨されていないようなのでまずは、前者の方法を試すことにしました。
作った地図アプリは、いろいろなファイルの集合体になっていて複雑怪奇な構造になっていて、自分でも触るのが嫌になるくらいなのですが、地図の表示部分は全部、OvarlayMapViewer.javaというActivityが持っているのでその中身だけをいじくることにします。まずは、ログを記録する部分です。これは、地図上に現在地の丸印を表示する部分にログを記録する処理を追加するだけです。地図上に現在地を表示するルーチンは、drawUserPositionMarker()というところにあります。まずは、経度と緯度だけを取得していたのを、高度と時刻も取得するようにします。高度の取得は、location.getAltitude()で行えます。時刻の取得も同様にGPSのデータを用いてlocation.getTime()で行うことができるのですが、これだと1970の1月1日を基準とした相対時刻(しかもUTC)が取得されるので、あとで計算をして日本の標準時に直す必要があります。まあ、どうせGPXファイルに直すのに何らかの方法で変換が必要になるので後処理は必要なんですが、ファイルネームも時刻を基準に設定しておこうと思っているので、前回までにやった方法と同じく、アンドロイドの端末の内部時刻を取得するようにしています。
また、ログを記録する部分ですが、配列(ArrayList)としてLogdataというのを作っておいて、そこに、緯度、経度、高度、現在時刻を順番に追記していきます。
この配列は倍精度浮動小数点になっています。理由は単純で緯度、経度、高度などは倍精度浮動小数点で値が返ってくるからです。また、この方が文字列に直すよりもメモリの消費量が少ないのでこうしています。ただし、現在時刻だけは、現在時刻を取得するルーチンが文字列を返すために、文字列を倍精度浮動小数点の値に変換しています。現在時刻を取得するルーチンは前回と同様のルーチンで、getNowTime()で現在時刻を文字列で返します。

    @RequiresApi(api = Build.VERSION_CODES.N)
    private void drawUserPositionMarker(Location location){
        if (globals.setGPS==true) {
            Latitude = location.getLatitude();
            Longitude = location.getLongitude();
     追加した部分~
            Height = location.getAltitude();
            nowTime = Double.parseDouble(getNowDate());
    ~追加した部分(ここまで)
            latLong15 = new LatLong(Latitude,Longitude);
         追加した部分~  
            Logdata.add(Latitude);
            Logdata.add(Longitude);
            Logdata.add(Height);
            Logdata.add(nowTime);
    ~追加した部分(ここまで)

次に、配列にため込んだログデータをファイルに書き出す部分です。試しに作ったアプリではSAFを使ってファイルを書き出したのですが、これのやり方はIntentを発行して、ファイル選択画面を出して、ファイル名を確定してから書き込むという方法をとっています。この時、onActivityResultを呼び出すのですが、このアプリの場合、onActivityResultがMainActivityに書かれていて、そこに飛んでいくようになっています。そうすると、OvarlayMapViewer内で使っていた変数が読めなくなります。解決方法の一つは、必要な変数をグローバル変数にしてしまうという方法です。また、Intentを介してデータを受け渡しするという方法もあるみたいです、ただし、どちらも結構記述が面倒なので(特に配列は)、今回はOvarlayMapViewerの中で解決できるように、一旦アプリ固有の外部ストレージにファイルを保存し、それを最終的に外から読める領域にコピーするということをしています。なので、前回2回でやったことのハイブリッドのような処理になってしまいました。onDestroy()の記述は下記のようになります。

    @RequiresApi(api = Build.VERSION_CODES.N)
    public void onDestroy() {


        try {
            if (locationUpdateReceiver != null) {
                unregisterReceiver(locationUpdateReceiver);
            }


        } catch (IllegalArgumentException ex) {
            ex.printStackTrace();
        }
        Intent intent = new Intent(getApplication(), LocationService.class);
        this.getApplication().unbindService(serviceConnection);
        stopService(intent);

        NotificationManager mNotificationManager = (NotificationManager) getSystemService(Context.NOTIFICATION_SERVICE);
        mNotificationManager.cancelAll();
        追加した部分~
        Ntime=getNowDate();
        String fileName = Ntime + ".log";
        Context context = getApplicationContext();
        file = new File(context.getExternalFilesDir(Environment.DIRECTORY_DOCUMENTS), fileName);
        if(isExternalStorageWritable()){
           
            {
                
                FileOutputStream output = new FileOutputStream(file,true);
                //OutputStreamWriter writer = new OutputStreamWriter(output);
                for (int i=0;i<Logdata.size();i++) {
                    String str = Logdata.get(i).toString();
                    output.write(str.getBytes(StandardCharsets.UTF_8));
                    output.write(",".getBytes(StandardCharsets.UTF_8));
                }

                output.flush();
                output.close();
                Logdata.clear();
               
            } catch (IOException e) {
                //e.printStackTrace();
                //textView.setText(R.string.error);
            }
            //String nfile = "storage/emulated/0/documents/"+fileName;
            String nfile = Path+"/"+fileName;
            java.nio.file.Path p1 = null;
            if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
                p1 = Paths.get(String.valueOf(file));
            }
            Path p2 = null;
            if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
                p2 = Paths.get(nfile);
            }

            try{
                if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
                    Files.copy(p1, p2);
                }
                file.delete();
            }catch(IOException e){
                System.out.println(e);
            }

        }
  ~追加した部分(ここまで)
        super.onDestroy();

    }

最初の部分は現在時刻を取得してファイル名を決めることをしています。
次のif(isExternalStorageWritable()){ 以下がファイルを書き出している部分で、ArrayListから順番にデータを読みだしてそれをアプリ固有の外部ストレージのDOCUMENTホルダーに現在時刻をファイル名にして書き込んでいくだけです。処理の方法は、前回テストした方法と同じです。違うのは最後に一括してファイルに書き出しているという点だけです。
ファイルを一旦アプリ固有の外部ストレージに書き込んだら、そのファイルを別の場所にコピーしています。処理は前回と基本的に同じですが、コピー先のフォルダ指定を前回同様のstorage/emulated/0/documents/としたところ、実機(Xiami RedMi Note11)では、内部ストレージの方に書き込まれてしまいました。ですので、マップファイルやGPXファイルを置いてある場所がPathという変数に入っているのでここに書き込むようにしています。
各処理にいちいちif(Build_VERSION_SDK_INT>=....)と書かれているのはAndroid Studioがエラーを吐いてサゼスチョン通りにするとこうなってしまっただけです。非常に見栄えが悪いのですが面倒なのでそのままにしてあります。
最後に、現在時刻を取得するルーチンと、アプリ固有の外部ストレージが書き込み可能か判定するルーチンを追記します。これららは前回と全く同じ処理です。

    @RequiresApi(api = Build.VERSION_CODES.N)
    public static String getNowDate() {
        final DateFormat df = new SimpleDateFormat("yyyyMMddHHmmss", Locale.getDefault());
        Date date = new Date();
        return df.format(date);
    }
    public boolean isExternalStorageWritable() {
        String state = Environment.getExternalStorageState();
        return (Environment.MEDIA_MOUNTED.equals(state));
    }

一応これでログが取れるようになりました。しばらくこれでテストしてみることにします。
また、このログファイルはただのデータの羅列なので、後から地図に表示させようとするとGPXなどの形式に変換する必要があります。その辺はPCを使ってPythonでやろうと思っています。