自転車乗りなので所有する自転車について語ってみる その2

前回の続きです。

kmaebashi.hatenablog.com

スポーツ自転車としては最初に買ったRF-7、かなり気に入って乗り回していましたが、それなりに走るようになると、やっぱりロードバイクというものに乗ってみたくなります。正直最初にRF-7を買った時から、「これをちゃんと乗るようなら2~3年のうちにロードを買おう」と思っていたのですが、2年目の2015年の夏は仕事が大変にアレなことになってまるで乗れなかったので、3年乗って、2017年の春、3/15にロードを買いに行きました。なぜ日付まで覚えているかというと、この日は確定申告のために有休をとった日で(いつも期限ぎりぎりに申請書作って投函してた)、確定申告後にワイズロードに行ったためです。思いのほか税金取られてしょげていたので、当初はULTEGRA搭載ぐらいのグレードのやつを買おうと思っていたのに結局105のやつになりました。ちなみにULTEGRAだの105だのいうのはコンポーネント(変速とかブレーキとかその辺の部品一式)のシマノというメーカーのグレードで、ランクとしては上から順に

という順になります。シマノは日本のメーカーですが(釣り具なんかも作ってますが、ロードバイクの部門の方がはるかにでかいらしい)、世界のシェアの過半も占めていて、だいたいブレーキレバーの前面かチェーンリング(ペダルにつながった、でかい方のギア)に上記グレード名が書いてあるので、その辺に停めてあるロードバイクを見ても、だいたいそれで値段とかが想像できます。まあ、一点豪華主義でコンポだけ高いの乗せたとか*1、コンポは安いけどフレームは高いんだ、というケースもあるかもしれませんけど。

そんなこんなで結局買ったのが、TREK EMONDA SL5(2017年版)です。

f:id:kmaebashi:20180715103423j:plain

この写真は、しまなみ海道の「サイクリストの聖地」で撮ったもの。

2017年のモデルなので、メーカーであるTREKの公式ページはもう消えてしまっていますが、コンポが105、カーボンフレームで重量が実測7.68kgと、この時代のロードバイクとしては安物でもなく高すぎもせず、まあミドルレンジのロードバイクなのだと思います。前回のRF-7同様色は赤です。RF-7が赤になったのはたまたま店頭在庫が赤だったという理由でしたが、ロードはむしろ色で選びました。RF-7を乗っているうちに「俺の自転車は赤!」という意識が刻み込まれたようです。

ところで上の写真、見る人が見れば「えらくサドルが低いな」「ステム(ハンドルとステアリングの回転軸をつなぐ部品)長すぎない?」と思うかと思います。要は私が足が短いのでサドルは上げられず、胴体が長いのでステムを長いのに交換してハンドルを前に出しているのです。ロードバイクを買えばまともな店なら体を計測してこうやってポジションを出してくれるのですが、これはちょっと傷ついた*2

ロードに乗り換えてからも、以前からの定番の行き先、養老とか関ヶ原とか香嵐渓とかは行っています。鈴鹿の8時間エンデューロも出ました。それに加えてロードで行ったところと言えば、たとえばしまなみ海道とか。

f:id:kmaebashi:20190915064034j:plain

しまなみ海道は結局3回行っていて、初回は2018年7月、当日の天気はよかったのですが先週の関西豪雨のため断水で飲食店の類が広島側ではかなり休業していて(大島まで行ったら開いてた)、リベンジのつもりで2019年の7月に行ったら今度は当日の天気が悪く雨の中片道だけ走行して帰りは輪行、さらにリベンジということで2019年9月に再々挑戦、となったのでした。その辺のことは以前書いています。

kmaebashi.hatenablog.com

kmaebashi.hatenablog.com

kmaebashi.hatenablog.com

kmaebashi.hatenablog.com最後でリベンジを果たした、と言えればよいのですが、3回目は1日で往復したのでちと行程に余裕がなく(もう陽が短くなった9月に行ったというのもありますし)、また行きたいなあ、と思っていたら2020年はコロナ禍でどこにも行けず。

アルプス安曇野センチュリーライド(AACR)にも参加しました。まず2018年は120kmで参加(申し込んだときにはこれしか空いてなかった)。

f:id:kmaebashi:20180520064119j:plain

エイド(補給所)の「駐輪場」。ロードバイクなんて普通はスタンド付けないので、こうなる。

f:id:kmaebashi:20180520084734j:plain

白馬エイドの写真かな。

f:id:kmaebashi:20180520085140j:plain

白馬エイドの補給食。豚汁。(この他に紫米のおにぎりとかもありました)

f:id:kmaebashi:20180520101823j:plain

AACR参加者には有名なヤマザキYショップのおやき。実のところ私は当時それを知らずに、トイレを借りたくて駆け込んだコンビニがここだったのですが。

f:id:kmaebashi:20180520132254j:plain

ゴールでもらえたアイスたい焼き。翌年、160kmを走ってみたら、アイスたい焼きではなくて普通のたい焼きになっていた。

f:id:kmaebashi:20180520134138j:plain

ゴール地点にて。

2019年には160kmで参加しました。これについてはこちらで。

kmaebashi.hatenablog.comその他、琵琶湖一周(ビワイチ)もやった。それはこちらで。

kmaebashi.hatenablog.comところでこのロードバイク、買った時は普通のペダル(フラットペダル)を付けたのですが、途中でシューズも買って、ビンディングペダルに交換しました。

f:id:kmaebashi:20170903174837j:plain

これを使うと、ペダルとシューズが固定されるので、踏み外す心配がないとか、引き足が使えるので速く走れるといったメリットがあります。反面、急停車した時にとっさに足が出ないという危険もあるわけで、公道でこれを使うのは賛否があるところだと思います。私はと言えば、正直これを使ったからといって速く走れる気はしません。ただ、どうもフラットペダルだとガニ股になるのか膝が痛くなるというのがあって、ずっとビンディングを使っています。2回ほど立ちゴケしたけれど。1回目はビンディングシューズ買ってすぐの練習中で、買ったばかりのデフォルト状態でまさか固定力が最大に設定してあるとは思わず外せなかったためで、2回目は、本当に固定されていることを忘れていて、なんということもない対向自転車とのすれ違い時に止まろうとしてコケたのですけど。

カニカルな話をするなら、いまどきのロードバイクは、変速が手元でできるように、変速レバーがブレーキレバーと一体化しています。

f:id:kmaebashi:20170402141346j:plain

ブレーキレバーの内側に小さなレバーがあって、これを内側に押し込むと、右手側の場合、後ろのギアが一つ重くなります。

f:id:kmaebashi:20170402141355j:plain

軽くするときはどうするかというと、ブレーキレバー自体を内側に押し込みます。

左手側は逆で、小さなレバーを内側に押し込むと軽くなり、ブレーキレバー自体を内側に押し込むと重くなります。なんで逆にするのか、と思うかもしれませんが、「今より大きいギアに替えるときは大きなレバーが必要」ということなのだと思います。

さて、このロードEMONDA SL5も3年あまり、走行距離では13,000kmほど乗ってきて(今年はコロナのせいで京都にも琵琶湖にもAACRにもしまなみにも行けず、あまり走れていませんが)、消耗品は変えたものの壊れているわけでもないので、まだまだ乗り続けるつもり――なんですが、ロードバイクも世間じゃすっかりディスクブレーキが主流になっていて、そういう、特に安全にかかわるようなところで「口実」ができてしまうと、新しいの買っちゃおうかなあ、という気に――いやでもまだまだ今のを乗り続けますよ!!

*1:RF-7は割とこれよりかも

*2:RF-7を買う時もだいぶサドルを下げられたので、わかっちゃいたんですけどねえ。

自転車乗りなので所有する自転車について語ってみる その1

 

昔、個人が「ホームページ」を作るのが流行ったころ、流行に乗って「ホームページ」を作っては見たもののその辺の普通の人にわざわざWebで世界に公開するようなことがそうそうあるわけもなく、たいていその手の「ホームページ」は、自己紹介と日記と掲示板、あとはそれに加えて「自分のパソコンを紹介するページ」があるのが定番でした。その時代にわざわざ「ホームページ」を作るような人なら、自分のPCにはそれなりの愛着を持っていたでしょうし、語りたかった、ということでしょう。まあ、読む方にとってはたいてい「そんなのどうでもいい」話ではあったのですが。

現在私は趣味で自転車に乗っているので、所有する自転車について語ってみます。もちろん、読む方にとっては「そんなのどうでもいい」話だとは思いますが、それを言うならこんなブログすべてがそうだろうしな。

私は現在2台の自転車を所有しており、1台は一応クロスバイク、もう1台はロードバイクです。クロスバイクの方を先に買いましたので、今回はそのクロスバイクについて。

このクロスバイクが、私が最初に買ったスポーツバイクになります。それまでママチャリは乗っていましたが、これを買うのと同時に知り合いにあげてしまいました。スポーツバイクを買おうと思ったきっかけについてはまた別途語るとして、最初に買ったクロスバイクはラレー(Raleigh)というイギリスのメーカーのRadford-7(略称RF-7)の2014年モデルでした。これ。

www.raleigh.jp最初の自転車をクロスバイクにしたのは、ドロップハンドルのロードなんて「いかにも自転車乗り」という自転車を買うことに抵抗があったためです。そういう意識はずっとあって、この自転車には「ふつうこういう自転車には付けないんですけどねえ」とショップの兄ちゃんに言われながらもスタンドを付けたし、サイクルウェアを買ったのも1年以上後でした。

それにしてもこのRF-7、クロスバイクとはいえ総重量9.3kgのアルミフレーム、フロントフォークはカーボン、コンポーネント(変速機とかブレーキとかその辺の部品一式を指します)はシマノのTiagra、タイヤは700x25cと、ハンドルがドロップハンドルでないことを除けば中身はほとんどエントリーモデルのロードバイクです。実際よく走りました。もともとクロスバイクといえば、マウンテンバイクとロードバイクのcrossover(両方の特徴を持つ)という意味だったわけですが、RF-7はクロスバイクとはいえかなりロード寄りです。カタログには確か「クロスバイクロードバイクのクロスオーバー」と書いてあったので3/4くらいはロードバイクなのでしょう。人によってはこれくらいの自転車のことは「フラットバーロード」と呼びます。

f:id:kmaebashi:20140721152714j:plain

購入後すぐの写真。まだサイクルコンピュータもリアフラッシャも付いてない。サドルも低いな(今でも低いですが)。

2014年の夏にこれを購入し、最初に行ったのが長島温泉。名古屋の自宅から長島温泉までの約30kmというのは、実は以前ママチャリ買って2日目に行った距離でもあります。この自転車ならさぞかし早く往復できるだろう、と思ったら、所要時間にそう差はなく、しかもお尻が痛くなった。本当に、スポーツバイクというのはお尻が痛くなるものなのです。まあこの頃は、信号待ちとかでちゃんとサドルの前に尻を落とすこともできていなかったので、無理もないのですが。

その後、犬山城とか明治村とかリトルワールドとかモンキーセンターとかの犬山方面(距離はまあ、片道30kmほど)をひととおり回って、養老の滝(40kmほど)に行って、知多半島の先端の「豊浜魚ひろば」(60kmくらい)に行ったくらいで夏終了、秋には香嵐渓に行ったり、その後もいろいろなところに行きました。香嵐渓、養老、養老からちょっと先に進んだ関ケ原あたりが、今後の定番コースになります。

f:id:kmaebashi:20140811135417j:plain

2014年に明治村で食べた牛鍋。

f:id:kmaebashi:20140813132751j:plain

養老の滝。最近でも養老はしょっちゅう行っていますが、滝はほとんど見に行っていません。

 

f:id:kmaebashi:20140831123356j:plain

豊浜魚ひろば(魚の市場です)。最近は塗りなおして色が変わっていますね。

f:id:kmaebashi:20140914140205j:plain

リトルワールドでは世界各国の料理が食べられる――はずなのですが、台湾のところにこれがあるのは信頼を損なうと思う。

f:id:kmaebashi:20140921134151j:plain

犬山城

f:id:kmaebashi:20140921145900j:plain

モンキーセンター。こういうのもまあシャレが効いている。

2015年のゴールデンウィークには、初めて京都に行っています。片道150km。

f:id:kmaebashi:20150501180642j:plain

京都行きについては以前こちらでまとめました。

kmaebashi.hatenablog.com京都には、だいたいゴールデンウィークと夏休みは毎回行っていたのですが、2015年の春に初挑戦(片道)、2015年の夏は仕事が大変にアレなことになって夏休み自体が消滅、2016年の春は往復しようとしたところ帰路が暴風向かい風で、手間取っているうちに雨が降ってきてあと10kmのところでリタイヤ、2016年夏に、ようやくまともに往復できました。その後、2017年春にはロードを買うので、RF-7で京都に行ったのは3回という事になります。でも、ロードに乗り換えても、150km以上の距離を走ったのはアルプスあずみのセンチュリーライドの160kmと琵琶湖一周(ビワイチ)の200kmくらいで、これもどちらもRF-7でも走れないことはなかったでしょう。それくらい、ロードに匹敵する性能の自転車です。2回目の京都行きの帰路、暴風向かい風は、ロードだったら下ハンを持つことで多少はマシにできたかもしれませんが。

RF-7では、鈴鹿の8時間エンデューロにも参戦しています。

f:id:kmaebashi:20161112074429j:plain

そういや一時この自転車は、むやみにパンクしていた時期があって、100kmくらいのライドで2回パンクして、スペアチューブを1本しか携行してなくて困ったことが2回あった。どうもタイヤがへたっていたのが原因だったようで、その後、やたら硬いタイヤ(Panasonicパセラ)に替えたらパンクとは無縁になったのでした。

f:id:kmaebashi:20160604074023j:plain

パンクしたら、こんなふうにひっくり返してホイールを外し、チューブを交換します。

f:id:kmaebashi:20140913145021j:plain

チューブを替えた後は、携帯用の小さな空気入れで空気を入れるのは大変なので、炭酸ガスの使い捨てボンベを使ったCO2インフレータで空気を入れます。私のインフレータは本来黒いのですが、一度使うと断熱膨張による冷却でこんな風に凍り付きます。

ディレイラーが後輪に接触でもしたのか、ねじ曲がって完全に走れなくなった(押して歩くこともできなくなった)こともあった。あの時は凹んだ。

f:id:kmaebashi:20161027194305j:plain

この時は結局ホイールまで交換することになりました。

f:id:kmaebashi:20170325134740j:plain

これは、ロードバイクを注文後、届くまでの間にRF-7で最後に行った香嵐渓の時の写真です。

そうそう、買って早々、この自転車にはサイクルコンピュータを付けました。距離計兼速度計です。

f:id:kmaebashi:20141004131625j:plain

前輪のスポークに小さな磁石を付けて、フロントフォークにセンサーを付けて、センサーの前を磁石が何回通ったかで距離を計測します。

f:id:kmaebashi:20141004132319j:plain

ピントが地面に合ってしまって、ピンボケですが。

ロードバイク購入後、この自転車は、近場に行くのにちょくちょく使ってはいるものの、やっぱり使用頻度は激減してしまっていますね。定期的にグリスアップはするのですが、その度に、さっぱり汚れていないなあ、と思いながら掃除して注油しています。ひとりで同時に2台の自転車に乗れない以上、仕方がないことではあるのですが。

f:id:kmaebashi:20180419142309j:plain

2018年4月に、近場(まあ家から10kmくらい?)まで乗った時の写真。

のんびり走るには本当はちょっとレーシーすぎる自転車なのですが、傍から見れば単なるクロスバイクなので、のんびり走るのにちょうどいいですね。

 

大阪万博ロゴ いのちの輝きくんブームに(いまさら)便乗してみました

大阪・関西万博のロゴマークで皆さん遊んでいる中、

nlab.itmedia.co.jp

仕事で時間が取れないし、と傍観していたわけですが、週末にちょっと参戦してみました。祭りには乗り遅れているうえ、たいした内容でもないのですが。

こちらから見てみてください。

大阪万博ロゴ いのちの輝きくんブームに便乗してみました

個人的にはver.2がお気に入りなのですが、一番わかりにくいかもしれませんね。

bisonとflexで列番号を知る方法

拙著「プログラミング言語を作る」は中国で翻訳出版されていて、先日、中国の読者さんから質問メールをもらいました(英語で)。
この質問者さんからメールをもらうのは2回目で、前回は1年ほど前、その時点で「I'm a middle school student in China.」とのことでした。優秀だなあ。


質問内容は、

エラーメッセージに行番号だけでなく列番号も出したいが、どうすれば取得できるか?

というもの。前回のメールおよび私の本の内容から彼*1はbisonとflexを使っていると思われるので、それを前提に回答しました。私自身やったことはなくて今回調べたので、せっかくなのでここに書いておきます。

まず、bisonでは、@n表記により、トークンの位置を取得できます。たとえば「プログラミング言語」で作っている型なし言語crowbarのパーサでは、以下のようにして関数の仮引数リストをパースしていますが、
http://kmaebashi.com/programmer/devlang/crowbar_src_0_1_01/S/2.html

parameter_list
        : IDENTIFIER
        {
            $$ = crb_create_parameter($1);
        }
        | parameter_list COMMA IDENTIFIER
        {
            $$ = crb_chain_parameter($1, $3);
        }
        ;

ひとまずfprintf()でトークンの列番号を表示するとしたら、以下のように書けます。

parameter_list
        : IDENTIFIER
        {
            $$ = crb_create_parameter($1);
            fprintf(stderr, "%s:%d..%d\n", $1, @1.first_column, @1.last_column); 
        }
        | parameter_list COMMA IDENTIFIER
        {
            $$ = crb_chain_parameter($1, $3);
            fprintf(stderr, "%s:%d..%d\n", $3, @3.first_column, @3.last_column); 
        }
        ;

@1とか@3とかは、$1とかと同様にルールの何番目かの記号を表していて、その型はLLYTYPE、y.tab.hでは以下の定義になっていました。

typedef struct YYLTYPE YYLTYPE;
struct YYLTYPE
{
  int first_line;
  int first_column;
  int last_line;
  int last_column;
}

これで、トークンの開始位置、終了位置が取れる…なら話は簡単なのですが、これを使うには当然lexerの協力が必要で、flexはこれをサポートしていません。そこで、自力で数え上げる必要があります。
とはいってもそう難しくはなくて、croabar.lをサンプルにするなら、
http://kmaebashi.com/programmer/devlang/crowbar_src_0_1_01/crowbar_0_1_01_l.html

以下のように書けます。

int current_column = 1; /* 現在の列位置を保持するグローバル変数 */
#define YY_USER_ACTION {\
  yylloc.first_column = current_column;\
  yylloc.last_column = current_column + yyleng - 1; \
  current_column += yyleng;
}
...
<INITIAL>\n {increment_line_number(); current_column = 1;}
...
<COMMENT>\n {
  increment_line_number();
  current_column = 1;
  BEGIN INITIAL;
}

current_columnというのは現在の列位置を保持するグローバル変数です(flexやbisonが決めたものではなく、私が定義)。
YY_USER_ACTIONというのは、flexにおいて各アクションの実行前に必ず実行される処理を定義するマクロです。そして、yylengというのは、flexが提供する、現在のトークンの長さを意味するグローバル変数です。
よって、YY_USER_ACTIONで列位置を数え上げていき、改行のたびにそれを1に戻せばよいわけです。

しかしまあ、英語で質問メールをもらったら返信も英語でしなければいけないわけで、私の英語力だと結構疲れますね……f(^^;

*1:中国人の名前は知らないので、彼女かもしれませんが。

Software Design 2020年5月号に記事を書かせていただきました

久々のブログ記事です。

遅い報告になってしまいましたが、タイトルのとおり、SoftwareDesign 2020年5月号に記事を書かせていただきました。

SoftwareDesignトップページ

第1特集「データ型を正しく説明できますか?」の1章および2章を執筆しております。

2章のタイトルが「静的型付け/動的型付け言語の違い」で、中には「それぞれのメリット・デメリット」という項もあったりします。炎上しそうなネタですが、なるべく両論併記になるように書いたつもりです。

こういうこと↓を書いてた頃よりはだいぶ丸くなりました。

kmaebashi.hatenablog.com

よければ読んでやってくださいませ。

JavaでJPEGのExif情報を読む

Exif情報について

デジカメやスマホで撮影した画像は通常JPEGというファイルフォーマットで保存されますが、これにはExif(Exchangeable image file format)という付属情報が付与されていて、そこに画像のサイズやら撮影日時やら撮影したカメラのメーカーやら、場合によっては位置情報なんかも保存されていたりします。SNSなどに画像を上げるときには注意してください。
で、たいていのデジカメでは、画像の方向(カメラを縦にして撮影した縦長の写真か、横にして撮影した横長の写真か)といった情報もExifに保持しています。つまり、カメラを縦にして縦長の写真を撮っても、画像データそのものは横長画像と同じように保持されていて、Exif情報により表示時にひっくりかえす、ということが期待されているわけです。時々、そのあたりの扱いがずれていて、画像がひっくり返って表示されることがあります。
Javaでは、標準のライブラリでは、Exif情報をもとに画像を正しい向きにする、という機能はないようなので、ファイルフォーマットの勉強も兼ねて、自力でExifフォーマットを読み込んでみました。

Exifフォーマットについて

Exifのフォーマットについては、主に以下のページを参照しました。
www2.airnet.ne.jp
beyondjapan.com
以下の仕様書の英語をちまちま拾い読みしていたら、
https://www.exif.org/Exif2-2.PDF
日本語版があるでやんの。もともと日本の企画ですからね……
http://www.cipa.jp/std/documents/j/DC-008-2012_J.pdf

ソースにコメントを過剰に入れておいたので、それを見ればフォーマットもわかるかと思います。

作ってみて

  • Javaのbyte型が符号付き(しかない)っての、端的に頭おかしい。
  • カメラによるのでしょうが、大昔、初代AndroidスマホHT-03Aで撮った縦長写真にはExifに画像方向がついてなくて、画像そのものが縦向きになっていた。最初これに気づかず、画像方向どこだ…… と悩んでいた。
  • むやみにいっぱい出ているUserCommentの項目(タグNo.0x9286)、コメントなんて特に何も入れたおぼえはないし、文字コードはASCIIになっているが文字列として復元できないんだけど、なんだろこれ。個人情報とか含んでないよね……

ソース

package com.kmaebashi.exifreadertest;

import java.io.BufferedInputStream;
import java.io.DataInputStream;
import java.io.FileInputStream;
import java.nio.ByteOrder;

public class ExifReader {
    public static void main(String[] args) {
        String filePath = "JPEGファイルのパスをここに書いてください";

        try (DataInputStream inStream = new DataInputStream(
                new BufferedInputStream(
                        new FileInputStream(filePath)))) {
            // 冒頭12バイトの構成
            // 先頭2バイト: 0xff 0xd8 JPEGファイルはこれで固定
            // 次の2バイト: 0xff 0xe1 APP1(Application Marker Segment 1)のマーカー
            // 次の4バイト: APP1領域のサイズ(ビッグエンディアン)
            // 次の6バイト: Exifのマーカー('E', 'x', 'i', 'f', 00, 00)
            byte[] headersByte = new byte[12];
            int[] headers;
            int app1Size;
            if (inStream.read(headersByte) != headersByte.length) {
                System.err.println("ファイルが小さすぎます。");
                System.exit(1);
            }
            headers = unsignedByteArrayToIntArray(headersByte);
            if (headers[0] != 0xff || headers[1] != 0xd8) {
                System.err.println("JPEGファイルではありません。");
                System.exit(1);
            }
            if (headers[2] != 0xff || headers[3] != 0xe1) {
                System.err.println("APP1データが含まれません。");
                System.exit(1);
            }
            app1Size = read2Byte(headers, 4, ByteOrder.BIG_ENDIAN);
            if (headers[6] != (byte)'E'
                || headers[7] != (byte)'x'
                || headers[8] != (byte)'i'
                || headers[9] != (byte)'f'
                || headers[10] != 0x00 || headers[11] != 0x00) {
                System.err.println("Exifデータが含まれません。");
                System.exit(1);
            }
            byte[] app1DataByte = new byte[app1Size];
            if (inStream.read(app1DataByte) < app1Size) {
                System.err.println("ヘッダ情報に対し、ファイルサイズが小さすぎます。");
                System.exit(1);
            }

            // Javaのbyteは符号付きでまともにバイトを扱えないので、intの配列に変換する。
            int[] app1Data = unsignedByteArrayToIntArray(app1DataByte);

            // APP1の冒頭8バイトはTIFFヘッダ
            // 先頭2バイト: バイトオーダーを示す。
            //       0x49, 0x49('I', 'I')...リトルエンディアン(Intelの略らしい)
            //       0x4d, 0x4d('M', 'M')...ビッグエンディアン(Motorolaの略らしい)
            // 次の2バイト: 0x00, 0x2a TIFF識別コード(固定)
            // 次の4バイト:
            ByteOrder byteOrder = ByteOrder.LITTLE_ENDIAN; // make compiler happy
            if (app1Data[0] == 0x49 && app1Data[1] == 0x49) {
                byteOrder = ByteOrder.LITTLE_ENDIAN;
            } else if (app1Data[0] == 0x4d && app1Data[1] == 0x4d) {
                byteOrder = ByteOrder.BIG_ENDIAN;
            } else {
                System.err.println("バイトオーダーが不正です。");
                System.exit(1);
            }
            if (read2Byte(app1Data, 2, byteOrder) != 0x002a) {
                System.err.println("TIFF識別子が002aではありません。");
                System.exit(1);
            }

            // 最初のIFD(Image File Directory)である0th IFDのオフセットを取得。
            // これを含め、以後出てくるオフセットは、すべてAPP1の先頭を起点とする。
            // ここまで、0th IFDのオフセットを含めて8バイト使っているので、
            // その続きとなる0th IFDの先頭のオフセットはたいてい8。
            int offsetOf0thIFD = read4Byte(app1Data, 4, byteOrder);
            System.out.println("0thIFDのオフセット(たいてい8)…" + offsetOf0thIFD);

            // 0th IFDのタグの数を取得(先頭2バイト)
            int numOf0thIFDTags = read2Byte(app1Data, offsetOf0thIFD, byteOrder);
            System.out.println("0thIFDのタグの数…" + numOf0thIFDTags);

            System.out.println("**** 0th IFD ****");
            // 各タグについて内容出力。各タグは12バイトの固定長。
            for (int i = 0; i < numOf0thIFDTags; i++) {
                // 8はTIFFヘッダ、2はタグの数の分
                dumpIFDTag(app1Data, 8 + 2 + i * 12, byteOrder);
            }

            // Exif IFDがあれば、それも出力する。
            if (exifOffset >= 0) {
                int numOfExifIFDTags = read2Byte(app1Data, exifOffset, byteOrder);
                System.out.println("**** EXIF IFD ****");
                System.out.println("Exif IFDのタグの数…" + numOfExifIFDTags);
                for (int i = 0; i < numOfExifIFDTags; i++) {
                    dumpIFDTag(app1Data, exifOffset + 2 + i * 12, byteOrder);
                }
            }

        } catch (Exception e) {
            e.printStackTrace();
            System.exit(2);
        }
    }

    private static class TagType {
        public String name;
        public int size;
        public TagType(String name, int size) {
            this.name = name;
            this.size = size;
        }
    }

    // IDFのタグの型情報を保持する配列。
    // Exifの仕様書(JEITA CP-3451) https://www.exif.org/Exif2-2.PDF を参照。
    private static TagType[] tagTypeData = {
        null,
        new TagType("BYTE", 1),
        new TagType("ASCII", 1), // 末尾には'\0'が入る
        new TagType("SHORT", 2),
        new TagType("LONG", 4),
        new TagType("RATIONAL", 8), // 分数
        null,
        new TagType("UNDEFINED", 1),
        null,
        new TagType("SLONG", 4), // 符号付
        new TagType("SRATIONAL", 8),
    };

    private static int exifOffset = -1;

    private static void dumpIFDTag(int[] array, int offset, ByteOrder byteOrder) {
        // ひとつのIFDタグ(固定長12バイト)の構成は以下の通り。
        // 先頭2バイト: タグNo。定義はExifの仕様書(JEITA CP-3451)を参照。
        // 次の2バイト: そのタグの型。
        // 次の4バイト: そのタグに含まれる値の数。
        // 次の4バイト: 値またはオフセット。示すべき値が4バイトに収まる場合はここに
        //              格納され、収まらない場合は、値の場所を示すオフセットが格納される。
        int tagNo = read2Byte(array, offset, byteOrder);
        int tagType = read2Byte(array, offset + 2, byteOrder);
        int numOfValues = read4Byte(array, offset + 4, byteOrder);

        System.out.print(String.format("%04x", tagNo) + ":"
                           + tagTypeData[tagType].name + ":"
                           + numOfValues + ":");

        if (tagTypeData[tagType].size * numOfValues <= 4) {
            printTagValue(array, offset + 8, tagType, numOfValues, byteOrder);
            System.out.println("");
        } else {
            int valueOffset = read4Byte(array, offset + 8, byteOrder);
            printTagValue(array, valueOffset, tagType, numOfValues, byteOrder);
            System.out.println("");
        }

        // 0th IFDの中にExif IFDのオフセット(タグNo.0x8769)があったら
        // static変数exitOffsetに退避する。
        if (tagNo == 0x8769) {
            exifOffset = read4Byte(array, offset + 8, byteOrder);
        }
    }

    private static void printTagValue(int[] array, int offset, int tagType, int numOfValues, ByteOrder byteOrder) {
        for (int i = 0; i < numOfValues; i++) {
            if (i > 0) {
                System.out.print(", ");
            }
            if (tagTypeData[tagType].name == "ASCII") {
                if (array[offset + i] == 0) {
                    System.out.print("'\\0'");
                } else {
                    System.out.print("" + (char)array[offset + i]);
                }
            } else if (tagTypeData[tagType].size == 1) {
                int value = array[offset + i];
                System.out.print(String.format("%02x", value));
            } else if (tagTypeData[tagType].size == 2) {
                int value = read2Byte(array, offset + (i * 2), byteOrder);
                System.out.print(String.format("%04x", value));
            } else if (tagTypeData[tagType].size == 4) {
                int value = read4Byte(array, offset + (i * 4), byteOrder);
                System.out.print(String.format("%08x", value));
            } else if (tagTypeData[tagType].size == 8) {
                int numerator = read4Byte(array, offset + (i * 8), byteOrder);
                int denominator = read4Byte(array, offset + (i * 8) + 4, byteOrder);
                System.out.print("" + numerator + "/" + denominator);
            }
        }
    }

    private static int[] unsignedByteArrayToIntArray(byte[] src) {
        int[] dest = new int[src.length];

        for (int i = 0; i < src.length; i++) {
            dest[i] = Byte.toUnsignedInt(src[i]);
        }

        return dest;
    }

    public static int read2Byte(int[] array, int offset, ByteOrder byteOrder) {
        int ret;
        if (byteOrder == ByteOrder.BIG_ENDIAN) {
            ret = array[offset] * 256 + array[offset + 1];
        } else {
            ret = array[offset + 1] * 256 + array[offset];
        }
        return ret;
    }

    public static int read4Byte(int[] array, int offset, ByteOrder byteOrder) {
        int ret;
        if (byteOrder == ByteOrder.BIG_ENDIAN) {
            ret = array[offset] * (256 * 256 * 256) + array[offset + 1] * 65536
                    + array[offset + 2] * 256 + array[offset + 3];
        } else {
            ret = array[offset + 3] * (256 * 256 * 256) + array[offset + 2] * 65536
                    + array[offset + 1] * 256 + array[offset];
        }
        return ret;
    }
}

出力例

Galaxy Note 8で撮った縦長写真です。先日しまなみ海道に行った時のやつ。

0thIFDのオフセット(たいてい8)…8
0thIFDのタグの数…13
**** 0th IFD ****
0100:LONG:1:00000fc0
0101:LONG:1:00000bd0
010f:ASCII:8:s, a, m, s, u, n, g, '\0'
0110:ASCII:7:S, C, -, 0, 1, K, '\0'
0112:SHORT:1:0006 ←これが画像の向きを示す。6は、時計回りに90°回せば元に戻る向き。
011a:RATIONAL:1:72/1
011b:RATIONAL:1:72/1
0128:SHORT:1:0002
0131:ASCII:14:S, C, 0, 1, K, O, M, U, 1, C, S, G, 3, '\0'
0132:ASCII:20:2, 0, 1, 9, :, 0, 9, :, 1, 4,  , 1, 8, :, 0, 0, :, 5, 4, '\0'
0213:SHORT:1:0001
8769:LONG:1:000000ec
8825:LONG:1:0000175a
**** EXIF IFD ****
Exif IFDのタグの数…31
829a:RATIONAL:1:1/40
829d:RATIONAL:1:170/100
8822:SHORT:1:0002
8827:SHORT:1:00c8
9000:UNDEFINED:4:30, 32, 32, 30
9003:ASCII:20:2, 0, 1, 9, :, 0, 9, :, 1, 4,  , 1, 8, :, 0, 0, :, 5, 4, '\0'
9004:ASCII:20:2, 0, 1, 9, :, 0, 9, :, 1, 4,  , 1, 8, :, 0, 0, :, 5, 4, '\0'
9101:UNDEFINED:4:01, 02, 03, 00
9201:SRATIONAL:1:5321/1000
9202:RATIONAL:1:153/100
9203:SRATIONAL:1:93/100
9204:SRATIONAL:1:0/10
9205:RATIONAL:1:153/100
9207:SHORT:1:0002
9208:SHORT:1:0000
9209:SHORT:1:0000
920a:RATIONAL:1:430/100
927c:UNDEFINED:98:07, 00, 01, 00, 07, 00, 04, 00, 00, 00, 30, 31, 30, 30, 02, 00, 04, 00, 01, 00, 00, 00, 00, 20, 01, 00, 0c, 00, 04, 00, 01, 00, 00, 00, 00, 00, 00, 00, 10, 00, 05, 00, 01, 00, 00, 00, 5a, 00, 00, 00, 40, 00, 04, 00, 01, 00, 00, 00, 00, 00, 00, 00, 50, 00, 04, 00, 01, 00, 00, 00, 01, 00, 00, 00, 00, 01, 03, 00, 01, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00
9286:UNDEFINED:5107:41, 53, 43, 49, 49, 00, 00, 00, 0a, 00, 00, 00, <なんかいっぱい出てるので中略> 30, 20, 00
a000:UNDEFINED:4:30, 31, 30, 30
a001:SHORT:1:0001
a002:LONG:1:00000fc0
a003:LONG:1:00000bd0
a005:LONG:1:0000173c
a217:SHORT:1:0002
a301:UNDEFINED:1:01
a402:SHORT:1:0000
a403:SHORT:1:0000
a405:SHORT:1:001a
a406:SHORT:1:0000
a420:ASCII:24:G, 1, 2, Q, S, K, A, 0, 2, S, M,  , G, 1, 2, Q, S, K, D, 0, 1, S, A, '\0'

しまなみ海道にリベンジに行きました。今度こそ。(往復編)

昨年の7月、自転車乗りの聖地とも楽園とも言われるしまなみ海道にはじめて行き、当日は素晴らしく天気がよかったものの1週間前の洪水の影響でグルメライドとかがあまりできず、今年の7月リベンジに行ったら今度は当日の天気が悪かった、ということで、9/14~9/15にかけて再度リベンジに行きました。

kmaebashi.hatenablog.com

kmaebashi.hatenablog.com

kmaebashi.hatenablog.com今回は、天気を見極めてから日程を決めたわけですが、そんな直前では今治の宿は取れません。過去2回は、始発の新幹線で尾道まで行って1日かけて今治周辺まで走り、1泊して、帰りはまた1日かけて尾道まで戻ってくる、という計画でしたが(前回は雨のため帰りは輪行になりましたが)、今回はまず尾道で1泊して、早起きして早朝から走り始め、1日で今治までを往復する、というコースになりました。尾道駅近くのホテルから走り始めて、新幹線に乗れる新尾道駅まで戻って約146km、名古屋から京都までの150kmとだいたい同距離です。

しまなみ海道はもうちょっとのんびり回るべきコースのような気もしますが、たいへん天気が良かったので十分に楽しめました。

さて、出発。土曜の昼過ぎ、名古屋からのぞみで出て岡山でこだまに乗り換えます。

f:id:kmaebashi:20190914142832j:plain

新尾道駅輪行解除。

f:id:kmaebashi:20190914151352j:plain

15時過ぎには着いたので、ちょっとは尾道も散策できます。尾道ラーメンとやらを食べてみようと、どうやら元祖らしい朱華園に行ってみたら…… orz

f:id:kmaebashi:20190914154309j:plain

すきっ腹を抱えて、「尾道映画資料館」を観に来ました。

f:id:kmaebashi:20190914163338j:plain

f:id:kmaebashi:20190914163351j:plain

尾道の映画といえば転校生だろう大林宣彦だろう、と思ったら、小津安二郎推しであった。東京物語すら観てないよ……

おのみち歴史博物館にも行ってみました。正直、狭くて収蔵品も少ないわ、その収蔵品も仏像とかで、まあ。

f:id:kmaebashi:20190914163205j:plain

中でやってた「尾道マンガ大賞展」の方が見ごたえありました。地元の小中学生のマンガですが。

 

f:id:kmaebashi:20190914163217j:plain

私にとってはやっぱり尾道といえば転校生なので、一夫と一美が転がり落ちた石段、御袖天満宮を見に行きました。

f:id:kmaebashi:20190914174025j:plain

これが石段。いやかなり急だよ怖いよ。

f:id:kmaebashi:20190914174031j:plain

角度がわかるように。

f:id:kmaebashi:20190914174339j:plain

石段のこの石に、継ぎ目がどこにもない。1本で切り出したらしい。元の石をどうやって運んでどうやって切り出してどうやって据え付けたのか。

別の店で尾道ラーメンも食べました。

f:id:kmaebashi:20190914175748j:plain

尾道ラーメンって平麺かと思ってましたが、ここのは違ったな。

f:id:kmaebashi:20190914180611j:plain

その後、ホテルの1Fの飲み屋さんで飲む。

f:id:kmaebashi:20190914192203j:plain

f:id:kmaebashi:20190914193517j:plain

でまあ酔っぱらって早々に寝てしまって、翌朝、出発です。

同じようにしまなみ海道を往復しようとする人の参考のため、ここからは各写真に、写真のタイムスタンプを付けていきます。

06:04 ホテルを出発。本当はもうちょっと早く出たかったのですが、微妙に準備に時間が取られた。

f:id:kmaebashi:20190915060447j:plain

06:10 尾道から最初の島である向島まではフェリーで渡ります。フェリー乗り場のアスファルトにあったイラストがこれ。

f:id:kmaebashi:20190915061040j:plain

06:11 この、尾道前6:06の始便に乗りたかったのですが、1本遅れた。まあ、12分間隔なので大した問題ではないですが。

f:id:kmaebashi:20190915061146j:plain

なお、尾道から向島まで渡るフェリーはいくつかありますが、私が使ったのはここです(写真は7月のやつ)。

f:id:kmaebashi:20190713093653j:plain

06:14 フェリーの桟橋から。もう9月なので、日の出も遅くなっていますね。

f:id:kmaebashi:20190915061407j:plain

06:16 フェリー来た。

f:id:kmaebashi:20190915061652j:plain

06:40 向島から因島に渡る、因島大橋が見えてきました(ここから、橋の入り口までが遠いんだけどさ)。

f:id:kmaebashi:20190915064033j:plain

07:27 因島から生口島へ渡る生口橋

f:id:kmaebashi:20190915072738j:plain

 生口橋から見た海。どうもしまなみ海道に来ると橋の写真ばかりになってしまいますが。

f:id:kmaebashi:20190915072735j:plain

07:30 生口橋から降りるところ。橋は高いところにかかっているので、橋を渡るたびに、こんな道でくねくねと登ったり下りたりします。

f:id:kmaebashi:20190915073046j:plain

08:07 次の橋。多々羅大橋

f:id:kmaebashi:20190915080720j:plain

多々羅大橋から見た海。

f:id:kmaebashi:20190915080726j:plain

08:22 多々羅大橋を渡ったところに、多々羅しまなみ公園の道の駅というのがあり、そこに「サイクリストの聖地碑」があるのですが、店はまだ開いてない。自販機で水だけ買って進みます。

f:id:kmaebashi:20190915082235j:plain

f:id:kmaebashi:20190915082244j:plain

f:id:kmaebashi:20190915082337j:plain

 最近はどこに行ってもゆるキャラはあるものですが、大三島分校って、学校のゆるキャラなの?

f:id:kmaebashi:20190915082647j:plain

08:46 大三島橋伯方島に渡ります。

f:id:kmaebashi:20190915084816j:plain

08:58 伯方島にあった「しまなみ造船」の造船所。でかい。

f:id:kmaebashi:20190915085843j:plain

09:10 伯方島の道の駅まで行って、腹も減ったしからあげでも食べようかと思ったら、その手の温かい食べ物は10:00からだそうで、仕方がないのでソフトクリームでカロリー補給。

f:id:kmaebashi:20190915091028j:plain

09:31 伯方・大島大橋で大島に渡ります。なんか曇ってきたな。

f:id:kmaebashi:20190915093118j:plain

f:id:kmaebashi:20190915093124j:plain

09:39 大島に着きました。

f:id:kmaebashi:20190915093904j:plain

10:15 あんまり腹が減ったので、大島の道の駅で海鮮丼。

f:id:kmaebashi:20190915101534j:plain

10:56 最後で最長の橋、来島海峡大橋。でかい(この写真は、橋の中ほどで撮影)。
こういうのを見ると、「人類の科学は、技術は、こんな巨大なものを作ることができるというのか…」と言いたくなりますね。

f:id:kmaebashi:20190915105615j:plain

来島海峡大橋からの海。

f:id:kmaebashi:20190915105612j:plain

11:17 愛媛まで渡って、今回は「サンライズ糸山」(レンタサイクル施設)で折り返します。今治駅まではまだ5kmくらいありますが、一応ここがしまなみ海道の終端らしい(?)。

f:id:kmaebashi:20190915111740j:plain

実のところ来島海峡大橋を渡ればサンライズ糸山はすぐなのですが、ちょっと時間がかかっているのは、まさかこんなにすぐとは思わず行き過ぎてしまったからです……

11:26 サンライズ糸山から見える来島海峡大橋
ここにはレストランもあって、本来ここで昼食というのも考えていたのですが、腹が減りすぎて大島で食べてしまった。が、レストラン混みすぎだったのでそれで正解だったかも。

f:id:kmaebashi:20190915112657j:plain

12:21 どこだか忘れましたが、天気が良かったので撮った写真。いや本当に今回は天気に恵まれました。

f:id:kmaebashi:20190915122139j:plain

13:15 こういう木漏れ日とか好きなんですよー。

f:id:kmaebashi:20190915131529j:plain

13:40 なぜか伯方島の道の駅を見逃して、やばい水がないと思いながら多々羅しまなみ公園(サイクリストの聖地碑があるところ)まで来てしまいました。喉はからからだし、愛媛なのでまずポンジュース

f:id:kmaebashi:20190915134056j:plain

13:46 多々羅しまなみ公園道の駅から見た海と多々羅大橋

f:id:kmaebashi:20190915134620j:plain

去年来た時と同じですが、でこたんソフトも食べる。

f:id:kmaebashi:20190915134907j:plain

14:56 7月に来た時に寄ったジェラードの店、また寄ろうと思ったら大行列。

f:id:kmaebashi:20190915145625j:plain

15:02 で、あきらめて同じく7月に寄った隣のお店で地ダコ天と生絞りジュースをいただきます

f:id:kmaebashi:20190915150208j:plain

15:56 因島のローソンで最後の補給。

f:id:kmaebashi:20190915155654j:plain

16:18 因島の謎の恐竜

f:id:kmaebashi:20190915161844j:plain

16:25 因島大橋を渡ります。

f:id:kmaebashi:20190915162533j:plain

16:39 渡り終えた因島大橋。天気が怪しくなってきている。

f:id:kmaebashi:20190915163935j:plain

18:15 新尾道まで行って、帰りの新幹線の席を取って、輪行準備をしているところ。この辺で雨が降り出した。ぎりぎり助かりました。

f:id:kmaebashi:20190915181524j:plain

帰りののぞみで、ワゴン車が全然こなくて、ようやく来たかと思ったら弁当売り切れだった。

f:id:kmaebashi:20190915202931j:plain

総走行距離146.39km。

帰ってきてみると、しまなみ海道に画鋲が撒かれたなんてニュースもありましたが……

私自身がロードバイク乗りであるためバイアスはあるかもしれませんが、ことしまなみ海道で地元の人に恨まれるような無茶をしている自転車はそうそういないと思うんだけどなあ。

今後とも、しまなみ海道が、自転車乗りの楽園であってほしいものです。