HOME システム関連アウトソーシング 開発実績 出張サポート アプリケーション お問合せ 雑記帳  

 今回の旅行での都市間移動には全て飛行機を使いました。タクシーブルースと呼ばれるミニバスでの移動の方が圧倒的に安上がりですし、マダガスカルの醍醐味を味わう上ではせめて1回くらいは体験したかったのですが、今回はゆっくり陸路で移動する時間的余裕がなく断念。まあ、仮に時間があったとしても、安全上の理由で現地事務所から夜間移動が禁止されているので、どっちにしてもディエゴスアレスやムルンダバのような遠い都市に行く際にタクシーブルースは利用出来ないのですが・・・。
 マダガスカルにはLCCは存在しないので、国内便はマダガスカル航空の独占状態。それ故ディスカウントが全くなく、高くつきますが仕方がありません。

 ディエゴスアレスの次はムルンダバへ、直行便はないのでアンタナナリヴ乗り継ぎの当日移動。マダガスカル航空は欠航や遅延が頻発との噂で、実際アンタナナリヴからディエゴスアレスへ移動した際は1時間半遅れ。アンタナナリヴ→ムルンダバ便は週3くらいの運航で「この便を逃すと日程的にもう行けない!」というリスキーな計画でしたが、この日は運よく時間通りの運航で無事ムルンダバへ到着出来ました。

 ムルンダバの目的は、もちろんバオバブ街道。空港で声をかけてきたガイドさんに、
 ・夜まだ暗いうちにホテルを出発→サンライズバオバブ鑑賞→キリンディ森林保護区を探索
   →各種バオバブ→サンセットバオバブ鑑賞
 という定番の日帰りツアーを組んでもらい、到着翌朝に出発。

 夜明けのバオバブ街道は早起きして見に行くだけの価値は充分ありました。私は山属性の人間なので、これまでに見てきた朝の絶景といえば槍ヶ岳山頂など山の風景ばかりでしたが、この夜明け+バオバブの組み合わせは絶妙、山とはまた違った魅力がありました。
  
 次の目的地キリンディ森林保護区へはバオバブ街道から1時間半くらい。道は舗装されておらず、かなり荒れている所もありました。マダガスカルの道は、どこもこんな感じなのでもう慣れました。
 ここでの主な目的はキツネザルです。これまでに行ったアンカラナ特別保護区とアンバー山国立公園では思ったほど見るチャンスがなかったので・・・。
 キツネザルは夜行性のものが多いので、本当はここに宿泊してナイトサファリをする方が沢山見られるそうですが、そこまでキツネザルにこだわっているわけではなく、あくまでもメインはバオバブなので、朝からの2時間ほどの探索で充分です。

 運が良かったのか、それとも普通のことなのかは分かりませんが結果的にシファカを含め3種類のキツネザルが見られました。その他、綺麗な鳥に出会うチャンスにも恵まれたので満足でした。どうも私は哺乳類よりも鳥類に興味があるらしい・・・。
 また、キリンディはフォッサというマダガスカル最大の肉食動物が見られることでも有名らしいのですが、森の中では会えず。ただ、食べ物を求めてか公園入口のロッジ周辺をうろついているフォッサを見ることは出来ました。

 その後は「愛し合うバオバブ」など数箇所のバオバブの名所を巡りましたが、夕暮れまでの時間が結構長く、またそんなに沢山行く場所があるわけでもないので、かなりダラダラした行程となりました。
 いろいろ案内してもらったのですがバオバブ三昧に段々飽きてきてしまい、「もうサンライズバオバブを見たし、夕暮れも同じような風景だろうから別に見ずに帰ってもいいかなあ」なんて思ったりもしたのですが・・・帰らなくて良かったです。サンセットバオバブは格別でした。
 夜明けのバオバブも最高ですが、太陽とバオバブ街道の位置関係的に夕暮れの方がより魅力的に感じました。夜明けと夕暮れのどちらか1つと言われれば夕暮れの方を選びますが、季節によって異なるかもしれません。
  
 当初計画に「ツィンギ・デ・ベマラ厳正自然保護区」が事務所から却下(渡航禁止)された時には、「飛行機が毎日飛んでなくて不便なムルンダバ行きは辞めて他の所行こうかなあ」と思いかけたのですが・・・。
 行って良かったです。やはりマダガスカルに行く以上、バオバブ街道は外せないと思います。

夜明けのバオバブ(1)

夜明けのバオバブ(1)


夜明けのバオバブ(2)

夜明けのバオバブ(2)


夜明けのバオバブ(3)

夜明けのバオバブ(3)


夜明けのバオバブ(4)

夜明けのバオバブ(4)


夜明けのバオバブ(5)

夜明けのバオバブ(5)


アカオイタチキツネザル

アカオイタチキツネザル
(キリンディ森林保護区)


ベローシファカ

ベローシファカ
(キリンディ森林保護区)


アカビタイキツネザルorチャイロキツネザル

アカビタイキツネザル。チャイロキツネザルかも?
(キリンディ森林保護区)


アカビタイキツネザルorチャイロキツネザル

アカビタイキツネザルorチャイロキツネザル。かなり近くまで近寄りましたが逃げませんでした。
(キリンディ森林保護区)


アカオオハシモズ

アカオオハシモズ
(キリンディ森林保護区)


マダガスカルシキチョウ

マダガスカルシキチョウ
(キリンディ森林保護区)


フォッサ

フォッサ。食べ物を求めてなのか、ロッジ周辺をウロウロ
(キリンディ森林保護区)


あくびをするフォッサ

あくびをするフォッサ
(キリンディ森林保護区)


愛し合うバオバブ

愛し合うバオバブ。2本のバオバブが絡み合っているかのような名前ですが、実際は1本が分岐したものです。


バオバブの花

バオバブの花。綿みたいな感じ。


フクロウ(マダガスカルコノハズク?)

フクロウ(マダガスカルコノハズク?)


マダガスカルサンコウチョウ(雄)

マダガスカルサンコウチョウ(雄)


バオバブ三昧

通常なら感動的風景なんですが、バオバブ三昧に少々飽きてきて・・・


バオバブ街道

バオバブ街道に戻ってきました。ここで夕暮れを待ちます。


バオバブへの落書き

バオバブへの落書き。残念ですが、日本語がなかったのは幸い。


バオバブ街道を歩く住民

地元の人々には見慣れた普通の風景なんでしょうね


夕暮れのバオバブ(1)

夕暮れのバオバブ(1)


夕暮れのバオバブ(2)

夕暮れのバオバブ(2)


夕暮れのバオバブ(3)

夕暮れのバオバブ(3)


夕暮れのバオバブ(4)

夕暮れのバオバブ(4)


夕暮れのバオバブ(5)

夕暮れのバオバブ(5)


夕暮れのバオバブと月

夕暮れのバオバブと月

 アンカラナ特別保護区でツィンギーを見た翌日はアンバー山国立公園へ。こちらはディエゴスアレスの街から比較的近く車で1時間くらい。
 この辺りはマダガスカルでも最北端、南緯12度~13度に位置しているとあって気温が高く、6月であってもディエゴスアレスの街では半袖1枚で過ごせますが(私はマラリア対策で長袖着てましたが)、ここアンバー山は標高が高いこともあり街よりも10度くらい低いのだそうです。また、雨が多いこともあり、距離的にはさほど離れていないアンカラナ特別保護区とは植生もかなり異なるとのこと。緑豊かな森が広がっていて、快適なハイキングを楽しめました。
 もちろん、マダガスカルですから固有種の宝庫、珍しい動物・植物にもたくさん出会えました。
 
 その翌日、ディエゴスアレス周辺滞在の最終日(4日め)は、フランス山というディエゴスアレス湾を見渡せるスポットへ。こちらは山というよりも丘といった感じの所で、1時間程度で登れます。頂上付近には、19世紀にフランス軍によって築かれた砦の跡が残っています。頂上からの湾の眺めは抜群なので、きっと見張りにも適していたことでしょう。

緑あふれる森

緑あふれる森
(アンバー山国立公園)


マダガスカルヒメショウビン

マダガスカルヒメショウビン(カワセミの仲間)
ピンボケになってしまいましたが、この鳥に会えたのが一番嬉しかったです。
(アンバー山国立公園)


ハガタムラサキ

ハガタムラサキ
(アンバー山国立公園)


指乗りカメレオン(ヒメカメレオン)

指乗りカメレオン(ヒメカメレオン)
ガイドさんは世界最小って言ってましたが、最近近くの島で最小のカメレオンが発見されたらしいので、世界で2番め?
(アンバー山国立公園)


カメレオン

普通のカメレオンはマダガスカル中にいるので、発見しても「ああカメレオンね」って感覚になってしまいます(笑)
(アンバー山国立公園)


ファラノーク

ファラノーク(マングースの仲間)。結構レアな動物らしく、ガイドさんは興奮してましたが、私は鳥の方が・・・
(アンバー山国立公園)


ガジュマル

他の木に絡みつくように寄生するガジュマル
(アンバー山国立公園)


ガジュマル

寄生された木は朽ち果て、ガジュマルだけが残された状態
(アンバー山国立公園)


カムフラージュするヤモリ

カムフラージュするヤモリ。ガイドさんに言われてから、認識出来るまで3分位かかりました。答えは最後に
(アンバー山国立公園)


滝

滝。地元の方にとって神聖な存在なのだそうです
(アンバー山国立公園)


T字バオバブ

フランス山の麓付近に生えるT字バオバブ


フランス山・山頂から望むディエゴスアレス湾

フランス山・山頂から望むディエゴスアレス湾


ヌシ・ルンガ

ディエゴスアレス湾に浮かぶ島、ヌシ・ルンガ
神聖な島で、ここに入ることはタブーとされているそうです。


フランス山・山頂の砦跡

フランス山・山頂の砦跡


カムフラージュするヤモリ

カムフラージュするヤモリ【答】 (上の写真を横にして拡大)
(アンバー山国立公園)

 ここまで職場の出勤日は皆勤で働いて来ましたが、赴任1年を前に初めて休暇を取りマダガスカルへ行ってきました。
 青年海外協力隊で任国外旅行先としてマダガスカルに渡航出来るのはモザンビーク隊のみなのですが、シニア海外ボランティアにはその縛りがありません。
 マダガスカルを選んだ1番の理由は「ツィンギーが見たい!」。以前に写真で見て以来、機会があれば行きたいと思っていた「ツィンギ・デ・ベマラ厳正自然保護区」。世界遺産にも登録されています。バオバブで有名なムルンダバから4WDで8時間くらい、ただし乾季でないと行けません。ということで、3月くらいから「6月に休暇を取るからね!」と職場で言い続けて日程調整し、乾季になるのを待っていたのですが・・・。
 
 JICAボランティアの任国外旅行には制限事項が多く、旅行の1ヶ月前までに旅行計画書を提出し現地事務所の承認を得る必要があります。そこで、計画書を作成し申請した所、不許可。安全上の理由でツィンギ・デ・ベマラ厳正自然保護区は渡航禁止とのこと。パックツアーにも普通に組み込まれている所で特に治安が悪いという話も聞いていないのですが、厳し過ぎです。まあ、僻地なんで何かあった時にサポート出来ない、といった理由かもしれませんが・・・。多分、黙って行ってしまってもバレないでしょうが、万一何かあった時に他の隊員等に迷惑をかけてしまう(制限が更に厳しくなってしまう等)ので泣く泣く計画変更、代わりにもう一つのツィンギーのある最北端のディエゴスアレス周辺(こちらは渡航OK!)へ行くことにしました。
 
 こちらのツィンギーはツィンギ・デ・ベマラのものより規模は小さいのですが、その代わり通常のツィンギーの他、色の異なるレッドツィンギーもあります。この両方を見るツアーを現地(ディエゴスアレス)旅行会社に組んでもらいました。

 初日はレッドツィンギー。首都アンタナナリヴからの飛行機が1時間半遅れましたが、この日の日程には余裕があったので問題なし。当初は、初日ツィンギー(アンカラナ特別保護区)、2日めレッドツィンギーという計画で依頼したのですが、旅行会社が出したプランはその逆。結果、これが正解でした。さすが、飛行機が良く遅れることも含め現地の状況を良く知っているようです。

 ディエゴスアレスからレッドツィンギーの入り口となる村までが1時間くらい。「国道6号線」なので幹線道路のはずなのですが・・・一応舗装道路なのですが、あり得ないくらいの凸凹。道路って整備しないとこんなに荒れちゃうものなのかと実感しました。
 村からレッドツィンギーまでは完全にオフロード。4WDでないと絶対行けませんし、雨季だったら4WDでも難しいんじゃないかと思うようなコースでした。
 ただ、それだけの道を行く価値ありました。長い時間をかけて形成されたものなのでしょうが、不思議な光景でした。ツィンギーの形状は針山地獄を思わせますが、色が赤いと更にそのイメージが強くなります。

 2日めはアンカラナ特別保護区へ。距離的にはディエゴスアレスの街から100kmくらいなのですが、やはり道路が荒れ放題なので4時間くらいかかりました。マダガスカルには魅力的な観光地がたくさんありますが、点在している上に道路状況は何処でもこんな感じのようなので移動がネックですね。
 こちらのツィンギーの色はグレー。石灰岩で出来ているそうです。色は地味ですがレッドツィンギーよりも規模が大きいので、また違った迫力がありました。こちらの方が固そうなイメージなので、上を歩いた時より痛いのはこちらの方かな?。もちろん歩きませんが・・・。
 ここの見どころはツィンギーだけでなく、コウモリの住む洞窟探検の他、キツネザルや鳥、カメレオン等の動物を見られる機会も多くあり、約5時間のハイキングを堪能しました。

レッドツィンギー

レッドツィンギー(1)
赤い峡谷


レッドツィンギー

レッドツィンギー(2)
谷の下部にはトゲトゲの岩


レッドツィンギー

レッドツィンギー(3)
トゲトゲ部分。針山地獄のよう(見たことありませんが・・・)


レッドツィンギー

レッドツィンギー(4)
鉄分を多く含んでそう。貧血に効く?


レッドツィンギー

レッドツィンギー(5)
近くで見るとそんなに尖っていないです(でも尖ってないのはここだけなのかも?)


レッドツィンギー

レッドツィンギー(6)
トゲトゲの上にも植物が・・・逞しい生命力です


ツィンギー

ツィンギー。石灰岩で出来ているそうです
(アンカラナ特別保護区)


ツィンギーの谷間に架かる吊り橋

ツィンギーの谷間に架かる吊り橋
(アンカラナ特別保護区)


ツィンギー

ツィンギー。別アングルからの眺め
(アンカラナ特別保護区)


謎の昆虫

謎の昆虫。トンボの仲間?
(アンカラナ特別保護区)


カンムリキツネザル

カンムリキツネザル
(アンカラナ特別保護区)


マダガスカルサンコウチョウ・雌

マダガスカルサンコウチョウ・雌
(アンカラナ特別保護区)


色鮮やかなヤモリ

色鮮やかなヤモリ
(アンカラナ特別保護区)


何かを飲み込んでいる最中のヘビ

何か(蛙?)を飲み込んでいる最中のヘビ
(アンカラナ特別保護区)


コウモリ洞窟入り口

コウモリ洞窟入り口
(アンカラナ特別保護区)


コウモリ洞窟内側からの眺め

コウモリ洞窟内側からの眺め
(アンカラナ特別保護区)


おびただしい数のコウモリ

おびただしい数のコウモリ。鳴き声と臭いが凄かったです
(アンカラナ特別保護区)


光る鍾乳石

コウモリ洞窟。光る鍾乳石
(アンカラナ特別保護区)

Game City 看板

Game City 看板


 Game City ・・・何かゲームセンターの名称のようですが、ハボロネの南端に位置するボツワナ最大級のショッピングモールのことです。

 出店店舗は南アフリカ資本のチェーン店ばかり・・・国内のショッピングモールは何処行っても規模の違いこそあれ入っている店舗は同じです。
 「ボツワナ資本はないの?頑張ってよ!」という気分になるのですが、人口が少ないのでチェーン店を展開するような大規模経営はボツワナでは難しいのかもしれません。
 

・CHOPPIES
 ボツワナで最も店舗数の多いスーパー。どこの街に行っても大概あります。数少ないボツワナ生まれのお店と聞いて最初応援してたのですが、経営はインド人だということを後で知って少しがっかり。
 いや、インド人でも全然OKなんですけど、ボツワナ人も頑張ってるな!って思ってたもので・・・。

CHOPPIES


 

・SHOPRITE
 南アフリカ最大のスーパーチェーン店。個人的に一番良く利用するスーパーです。他チェーンのスーパーと比較して野菜の鮮度がまともな気がします。

SHOPRITE


 

・CAPE UNION MART
 アウトドアグッズショップ。まともなメーカーの登山用品を扱っているのは嬉しいのですが値段が高い。
 きちんと調べたわけではないですが、日本の3割~5割増しといった印象。

CAPE UNION MART


 

・WOOLWORTH
 衣料の他、一部食料品も売ってます。オーストラリア資本の高級志向店。私は紅茶を此処で買っていますが・・・トワイニングのイングリッシュブレックファースト、それもティーバッグ。
 多分ボツワナでは美味い紅茶は手に入りません。

CAPE UNION MART


 

・Mr. Price
 衣料品店ですが、台所用品や風呂用品などの日用品を専門的に扱う店舗もあります。 価格を売りにしているような店名ですが、さほど安くはありません。

PEP STORE


 

・PEP STORE
 衣料品を中心に家庭用雑貨も扱っています。低価格。CHOPPIESの次くらいに全国各所で見かけます。

PEP STORE


 

・CLICKS
 ドラッグストア。重宝してます。

CLICKS


 

・GAME STORES
 ホームセンター。電化製品から食料品まで、この店だけで大概の生活用品は揃います。
 Game CityにあるからGAME STORESという名称なのかと思っていましたが、ここも南アフリカのチェーン店のようなので、たまたま「Game」が共通しただけのようです。

GAME STORES


 

・番外編
 通路の真ん中にあるサングラスショップ。大きなショッピングモールには、このような半露店的なショップがいくつかあります。
 上記Game Storesの写真を撮ってたら「私も撮ってよ!」と頼まれたので、おまけで掲載。

Street Stall

 前回までで、円の大きさが地球を1周するような大きなものにならない限りは等距離の円を同心円状に描くことが出来るようになりました。
 今回は、
 ・円の半径を地球を1周するような規模に設定した場合、本来とは逆の領域が選択されてしまう場合がある
 この不具合に対応したいと思います。

 ポリゴンを描く際に選択する領域をA、選択していない方の領域をBとした時、Google Maps APIではAとBの面積の差があまりないような場合には、A・Bのうち北側に位置する方の領域を選択する傾向にあるようです。そのため、南半球にある地点を円の中心点にする場合はAの方が南側になりますからBが選択されてしまいます。また、例えば東京から15000kmの等距離円などBの面積が明らかにAよりも小さくなる場合にはBが選択されます。

 google.maps.Polygonメソッドに「指定した緯度・経度がポリゴン内部に含まれるように領域選択する」オプションがあればいいのになあ・・・と思うのですが、残念ながら備わっていません。
 でも、そのオプションの有無を探している際に、google.maps.geometryライブラリのポリゴン関数にcontainsLocationというものがあるのを見つけました。

 ・Maps Javescript API ジオメトリ ライブラリより抜粋
 containsLocation(point:LatLng, polygon:Polygon) 
 この関数は、指定の地点がポリゴンの内部または境界に存在すれば、true を返します。
 
 これを利用すれば、次のようなロジックでポリゴン描画すれば、上記ケースにも対応出来るのではないかと考えました。

 1. 半径の大きい円のポリゴンをダミーで描く(visible=falseとして表示させない)
 2. 円の中心点がダミーポリゴンの内部に含まれているかどうかをcontainsLocation関数で調べる
 3. 内部に含まれている場合はそのままでOKなのでダミーと同じものをもう一度描き表示させる。
 4. 内部に含まれていない場合は逆の領域Bがポリゴンとして選択されてしまったことになる。
  領域Aを選択させるには、一旦地球(地図)の全領域を覆うポリゴンを描いた後、領域Bを中抜け
  領域としてくり抜いてしまえば良い。
 
 コードにすると次のようになります

   //地図全体を覆うポリゴン用座標データ(反時計回り)
   var polyWholeWorld = [
      new google.maps.LatLng(0, 180),
      new google.maps.LatLng(-90, 180),
      new google.maps.LatLng(-90, 90),
      new google.maps.LatLng(-90, 0),
      new google.maps.LatLng(-90, -90),
      new google.maps.LatLng(-90, -180),
      new google.maps.LatLng(0, -180),
      new google.maps.LatLng(90, -180),
      new google.maps.LatLng(90, -90),
      new google.maps.LatLng(90, 0),
      new google.maps.LatLng(90, 90),
      new google.maps.LatLng(90, 180),
      new google.maps.LatLng(0, 180)
   ];
   //ダミーのポリゴンを作成   
   var dummyCircle = new google.maps.Polygon({
      paths: polyCoords,  //ポリゴン領域の位置情報(緯度・経度)の配列(時計回り)
      visible: false
   });
   //中心点(myPoint)がダミーポリゴン内部に含まれているかどうかをチェック
   var innerFlag = google.maps.geometry.poly.containsLocation(myPoint, dummyCircle);

   if (innerFlag) {
      //内部にある場合は、そのままのデータでOK
      polyPath = [polyCoords];  
   } else {
      //内部にない場合は、地球全領域からダミーポリゴンの領域をくり抜く
      polyPath = [polyWholeWorld, polyCoords];  //polyWholeWorldとpolyCoordsは回りが逆
   }
   //等距離円の描画
   myCircle = new google.maps.Polygon({
      paths: polyPath,
      strokeWeight: 0,
      fillColor: red,
      fillOpacity: 0.4
   });
   myCircle.setMap(map);

 後は前回同様に内側の円領域を切り抜く処理を加える必要があります。通常のケースでは単に1つ内側の円領域データ(配列)を逆順にすれば良いのですが、「内側の円の段階で本来とは逆の領域が選択されてしまっている場合にはどうすればいいのだろうか?」という疑問が浮かびましたが、このケースでも正しい領域を描くために使用したデータ(地球全体を選択して領域Bをくり抜くパターン)をそのまま全て逆順にする次のような手法で上手くいきました。

 地球全体領域-外円の逆領域(領域B1)-{地球全体領域-内円の逆領域(領域B2)}

 この式を展開すれば 

 内円の逆領域(領域B1)-外円の逆領域(領域B1)

 これで良かったのか・・・でも考え方としては上式の方が分かりやすい気がするので、結果は同じですが今回は上式を採用しました。

計算・描画結果

計算・描画結果 ボツワナの首都(ハボロネ)から3000km,5000km,8000km,10000kmの同心円

 上手く出来たようです。しかし、ボツワナから10,000kmでは日本に全然届かない・・・やっぱり遠いなあ。

 前回、一応それっぽい同心円を描いてみたものの、等距離円の距離が世界地図規模(千km単位)となると計算誤差が大きくなるため、今回はもう少し正確性の高い計算方法を探索します。

 以前に位置情報(緯度・経度)の分かっている2点間の距離を知りたいと思った時には、その求め方が記載されたサイトはWeb上に多数存在し、結構簡単に見つけられたのですが、今回必要としている「ある1点からの距離がrである点の緯度・経度を求める方法」はなかなか見つけられず、一番頼りになる国土地理院のサイトにも記載なし。「もうここはおとなしくgoogle.maps.Circle利用で妥協するか(同心円の色が重なってしまいますが)」と諦めかけたところで漸く見つけました・・・Vincenty法(順解法)。一旦見つけてしまえば「何で今まで見つけられなかったんだろう?」と思うのですが、探し物にはよくあることですね。
 始点の緯度・経度、始点からの距離・方位角を、このVincenty法(順解法)の式に代入することで到達点の緯度・経度が求まります。
 同心円を描くには始点の周り360度の点を全て求める必要があるので、以下のように関数化しました。

  //Vincenty法(順解法)
  function VincentyDirect(p1,deg1,s) {
/*
     	引数
        p1    : 中心となる点(点1)の緯度・経度(google.maps.LatLngクラス)
        deg1  : 求める点の方角(真北からの時計回りの角度。北:0、東:90、南:180、西:270)
        s     : 点1からの距離(m)
*/
     var p2                         //求める点(点2)の緯度・経度(google.maps.LatLngクラス)
     var a = 6378137.0;             //長半径(赤道半径)
     var f = 1.0 / 298.257223563;   //扁平率(WGS84)
     var b = a * (1 - f);           //短半径(極半径)
     var phi1,phi2;                 //点1、点2の緯度
     var L1,L2;                     //点1、点2の経度
     var L;                         //点1、点2の経度差
     var alpha1,alpha2;
     var alpha;
     var U1,U2;
     var lambda
     var sigma;
     var sigma1,formersigma;
     var sigmam,deltasigma;
     var epsilon = 1.0e-12;         //計算精度(sigmaの許容誤差)
     var sinalpha,cosalpha2;
     var u2,A,B,C;
     var cnt=0;

     phi1 = calRadian(p1.lat());
     L1 = calRadian(p1.lng());
     alpha1 = calRadian(deg1);

     U1 = Math.atan((1 - f)*Math.tan(phi1));
     sigma1 = Math.atan2(Math.tan(U1),Math.cos(alpha1));
     sinalpha = Math.cos(U1)*Math.sin(alpha1);
     cosalpha2 = 1-Math.pow(sinalpha,2);
     u2 = cosalpha2*((Math.pow(a,2)-Math.pow(b,2))/Math.pow(b,2));
     A = 1+u2/16384*(4096+u2*(-768+u2*(320-175*u2)));
     B = u2/1024*(256+u2*(-128+u2*(74-47*u2)));

     sigma = s/(b*A);
     do {
        sigmam = (2*sigma1+sigma)/2;
        deltasigma = B*Math.sin(sigma)
                       *(Math.cos(2*sigmam)
                         +1/4*B*(Math.cos(sigma)*(-1+2*Math.pow(Math.cos(2*sigmam),2))
                                 -1/6*B*Math.cos(2*sigmam)*(-3+4*Math.pow(Math.sin(sigma),2))*(-3+4*Math.pow(Math.cos(2*sigmam),2))));
        formersigma = sigma;
        sigma = s/(b*A)+deltasigma;
        cnt++;
        if (cnt>100) {
           alert("error");
           return null;
        }
     } while (Math.abs(sigma-formersigma)>epsilon);

     phi2 = Math.atan2((Math.sin(U1)*Math.cos(sigma)+Math.cos(U1)*Math.sin(sigma)*Math.cos(alpha1))
                     ,((1-f)*Math.sqrt(Math.pow(sinalpha,2)+Math.pow((Math.sin(U1)*Math.sin(sigma)-Math.cos(U1)*Math.cos(sigma)*Math.cos(alpha1)),2))));
     lambda = Math.atan2(Math.sin(sigma)*Math.sin(alpha1),(Math.cos(U1)*Math.cos(sigma)-Math.sin(U1)*Math.sin(sigma)*Math.cos(alpha1)));
     C = f/16*cosalpha2*(4+f*(4-3*cosalpha2));
     L = lambda-(1-C)*f*sinalpha*(sigma+C*Math.sin(sigma)*(Math.cos(2*sigmam)+C*Math.cos(sigma)*(-1+2*Math.pow(Math.cos(2*sigmam),2))));
     L2 = L + L1;
     alpha2 = Math.atan(sinalpha/(-1*Math.sin(U1)*Math.sin(sigma)+Math.cos(U1)*Math.cos(sigma)*Math.cos(alpha1)));
     p2 = new google.maps.LatLng(calDegree(phi2),calDegree(L2));
     return p2;
  }

 アークタンジェントの関数はMath.atan()とMath.atan2()の2種類あり、これらを使い分ける必要がありますが、Wikipediaではその辺も親切に解説されています。
 Vincenty法での緯度・経度を計算するサイト(数箇所)での計算結果と比較することで検証しましたが、誤差はほとんどなかったので上記ソースで多分大丈夫かと思います。なお、扁平率はWGS84のものを採用しましたが、GRS80の扁平率を採用しても計算結果はほとんど変わりません。

 と、ここまで作成した段階で残念な発見・・・同様の計算をする関数がgoogle.maps.geometryライブラリに用意されていました。

 ・Maps Javescript API ジオメトリ ライブラリより抜粋
 computeOffset() を使用すると、特定の角度、出発地点、移動距離(メートル単位)から、目的地の座標を計算できます。

 Google Maps APIを読み込む際に、

<script type="text/javascript" src="http://maps.google.com/maps/api/js?language=jp&key=取得したAPIキー&libraries=geometry"></script>

 といった具合にlibraries=geometryのパラメータを追記するだけで利用可能です。google.maps.Circleも当関数を利用しているようです。
 computeOffset()と自作の上記関数とで誤差があるか検証したところ、低緯度の鉛直方向(北半球なら南側)の誤差が大きくなるようで、ハボロネ(ボツワナの首都)から5000km地点計算時には約31kmのズレが出ました。5000kmで31kmならば誤差は1%未満、地図上ではほとんど分からないレベルですが、原因が気になるのでいろいろ調べてみるとgoogle.maps.geometryライブラリでは地球を完全な球体とみなして計算していることが分かりました(自作関数の扁平率をゼロにして再度検証した結果は両者一致しました)。
 大差ないとはいえ、自作関数の方が扁平率を考慮している分だけより正確ということになるのか・・・わざわざ作った甲斐がありました。computeOffset()ではなく、こちらを採用します。
 
 ここまで出来てしまえば、地図に落とす処理は簡単です。
 今回のテスト用html、Javascriptのソースを以下に記載します。 

・concentriccircles_test2.html

<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
<html>
<head>
<title>concentric circles on the map</title>
<meta http-equiv="content-type" content="text/html; charset=UTF-8">
<meta name="viewport" content="initial-scale=1.0, user-scalable=no" />
<meta http-equiv="Content-Style-Type" content="text/css">
<script type="text/javascript" src="http://maps.google.com/maps/api/js?language=jp&libraries=geometry"></script>
<style type="text/css">
<!--
.circletitle {
   font-size:12px;
   color:#FFFF00;
   padding-left:20px;
   white-space: nowrap; 
}
.circletitle input {
   width:40px;
}
.circletitle input[type="button"] {
   width:60px;
   font-size:14px;
}

-->
</style>
<script type="text/javascript" src="js/concentriccircles_test2.js"></script>
</head>

<body style="margin:0px;" onload="initialize()">
  <div id="top_bar" style="background-color:#333399;padding:5px;">
  <table>
  <tr>
    <th style="color:#FFFFFF;text-align:left;padding-left:20px;" colspan="7" style="font-size: 16px;">中心となる地点から一定距離にある点を求めて地図上に同心円を描く(テスト2)~Vincentyの順解法利用~</th>
  </tr>
  <tr>
    <td class="circletitle">circle1</td>
    <td class="circletitle">circle2</td>
    <td class="circletitle">circle3</td>
    <td class="circletitle">circle4</td>
    <td class="circletitle">circle5</td>
    <td colspan="2">&nbsp;</td>
  </tr>
  <tr>
    <td class="circletitle">
        <input type='text' id="f_distance0">km
        <input type='text' id="f_color0">
    </td>
    <td class="circletitle">
        <input type='text' id="f_distance1">km
        <input type='text' id="f_color1">
    </td>
    <td class="circletitle">
        <input type='text' id="f_distance2">km
        <input type='text' id="f_color2">
    </td>
    <td class="circletitle">
        <input type='text' id="f_distance3">km
        <input type='text' id="f_color3">
    </td>
    <td class="circletitle">
        <input type='text' id="f_distance4">km
        <input type='text' id="f_color4">
    </td>
    <td class="circletitle">
       <input type="button" id="btnDrawCircle" name="btnDrawCircle" onclick="drawCircle()" value="draw">
    </td>
    <td class="circletitle">
       <input type="button" id="btnResetCircle" name="btnResetCircle" style="color:#ff0000;" onclick="resetCircle()" value="reset">
    </td>
  </tr>
  </table>
  </div>
  <div id="map_canvas" style="margin:5px;height:600px"></div>
</body>
</html>

・concentriccircles_test2.js

  var map;
  var svg;
  var Gaborone = new google.maps.LatLng(-24.6091799,25.7900739);
  var iniZoom = 4;
  var pointMarker = new google.maps.Marker();
  var circleInfo = new Array();
  var myCircle = new Array();
  var polygons = new Array();
  var drawFlag;

  function initialize() {

     drawFlag = false;
     var dColor = ["blue","green","red","yellow","magenta"];
     var dDistance = [500,1000,1500];

     for (var i = 0; i <= 4; i++) {
        if (dDistance[i]) {
           document.getElementById("f_distance"+i).value = dDistance[i];
        }
        if (dColor[i]) {
           document.getElementById("f_color"+i).value = dColor[i];
        }
     }

     var myOptions = {
        zoom: iniZoom,
        center: Gaborone,
        mapTypeControl: false,
        mapTypeId: google.maps.MapTypeId.ROADMAP
     };
     map = new google.maps.Map(document.getElementById("map_canvas"), myOptions);

     google.maps.event.addListener(map, "click", function(clickevent) {
        var myPoint = new google.maps.LatLng(clickevent.latLng.lat(), clickevent.latLng.lng());
        setPointMarker(myPoint);
     });

  }

  function setPointMarker(location, pile) {

    if(!pointMarker.getMap()){
       pointMarker = new google.maps.Marker({
          position: location,
          map: map,
          draggable: true,
       });
    }else{
       pointMarker.setPosition(location);
    }

  }

  function setCircleInfo() {
     
     var curDistance,curColor,curPplText;

     circleInfo = new Array();

     for (var i = 0; i <= 4; i++) {
        curDistance = parseFloat(document.getElementById("f_distance"+i).value)*1000;
        curColor = document.getElementById("f_color"+i).value;
        if (!curColor) {
           curColor = "#ffffff";
        }
        if (isFinite(curDistance)) {
           circleInfo.push({"distance":curDistance,"color":curColor});
        }
     }

     circleInfo.sort(function(a,b){
        if(a.distance<b.distance) return -1;
        if(a.distance > b.distance) return 1;
        return 0;
     });

  }

  function resetCircle() {

     var i;

     for (var i = 0; i < myCircle.length; i++) {
        if (myCircle[i]) {
           myCircle[i].setMap(null);
        }
     }
     myCircle.length = 0;
     
     if (pointMarker.getMap()) {
        pointMarker.setMap(null);
     }
     drawFlag = false;
  }

  function drawCircle() {

     var polyCoords = new Array();
     var polyPath = new Array();
     var degpitch = 0.5;//0.5度ピッチで計算

     if (!pointMarker.getMap()) {
        alert("中心となる点にマーカーを設置して下さい。");
        return 0;
     }

     if (drawFlag) {
        return 0;
     }

     var myPoint = pointMarker.getPosition();

     setCircleInfo();

     for (var i = 0; i < circleInfo.length; i++) {
        var polyCoord = new Array();
        //中心点から指定の距離だけ離れた地点の座標を計算し、配列に収納(ポリゴン用)
        for (var theta = 0; theta < 360; theta += degpitch) {  //thetaは真北からの角度(時計回り)
           polyCoord.push(VincentyDirect(myPoint,theta,circleInfo[i].distance));
        }
        polyCoord.push(polyCoord[0]);
        polyCoords[i] = polyCoord;

        polyPath = [polyCoords[i]];
        //最内以外の円描画時は、1つ内側の円の座標を反対周りにした配列を付加することでドーナツ型ポリゴンにする
        if (i > 0) {
           polyPath.push(polyCoords[i-1].slice().reverse());
        }

        myCircle[i] = new google.maps.Polygon({
           paths: polyPath,
           strokeWeight: 0,
           fillColor: circleInfo[i].color,
           fillOpacity: 0.4
        });
        myCircle[i].setMap(map);

     }

     drawFlag = true;

  }

  function calRadian(deg){
     return deg * Math.PI / 180.0;
  }

  function calDegree(rad){
     return rad * 180 / Math.PI;
  }

  //Vincenty法(順解法)
  function VincentyDirect(p1,deg1,s) {
/*
     	引数
        p1    : 中心となる点(点1)の緯度・経度(google.maps.LatLngクラス)
        deg1  : 求める点の方角(真北からの時計回りの角度。北:0、東:90、南:180、西:270)
        s     : 点1からの距離(m)
*/
     var p2                         //求める点(点2)の緯度・経度(google.maps.LatLngクラス)
     var a = 6378137.0;             //長半径(赤道半径)
     var f = 1.0 / 298.257223563;   //扁平率(WGS84)
     var b = a * (1 - f);           //短半径(極半径)
     var phi1,phi2;                 //点1、点2の緯度
     var L1,L2;                     //点1、点2の経度
     var L;                         //点1、点2の経度差
     var alpha1,alpha2;
     var alpha;
     var U1,U2;
     var lambda
     var sigma;
     var sigma1,formersigma;
     var sigmam,deltasigma;
     var epsilon = 1.0e-12;         //計算精度(sigmaの許容誤差)
     var sinalpha,cosalpha2;
     var u2,A,B,C;
     var cnt=0;

     phi1 = calRadian(p1.lat());
     L1 = calRadian(p1.lng());
     alpha1 = calRadian(deg1);

     U1 = Math.atan((1 - f)*Math.tan(phi1));
     sigma1 = Math.atan2(Math.tan(U1),Math.cos(alpha1));
     sinalpha = Math.cos(U1)*Math.sin(alpha1);
     cosalpha2 = 1-Math.pow(sinalpha,2);
     u2 = cosalpha2*((Math.pow(a,2)-Math.pow(b,2))/Math.pow(b,2));
     A = 1+u2/16384*(4096+u2*(-768+u2*(320-175*u2)));
     B = u2/1024*(256+u2*(-128+u2*(74-47*u2)));

     sigma = s/(b*A);
     do {
        sigmam = (2*sigma1+sigma)/2;
        deltasigma = B*Math.sin(sigma)
                       *(Math.cos(2*sigmam)
                         +1/4*B*(Math.cos(sigma)*(-1+2*Math.pow(Math.cos(2*sigmam),2))
                                 -1/6*B*Math.cos(2*sigmam)*(-3+4*Math.pow(Math.sin(sigma),2))*(-3+4*Math.pow(Math.cos(2*sigmam),2))));
        formersigma = sigma;
        sigma = s/(b*A)+deltasigma;
        cnt++;
        if (cnt>100) {
           alert("error");
           return null;
        }
     } while (Math.abs(sigma-formersigma)>epsilon);

     phi2 = Math.atan2((Math.sin(U1)*Math.cos(sigma)+Math.cos(U1)*Math.sin(sigma)*Math.cos(alpha1))
                     ,((1-f)*Math.sqrt(Math.pow(sinalpha,2)+Math.pow((Math.sin(U1)*Math.sin(sigma)-Math.cos(U1)*Math.cos(sigma)*Math.cos(alpha1)),2))));
     lambda = Math.atan2(Math.sin(sigma)*Math.sin(alpha1),(Math.cos(U1)*Math.cos(sigma)-Math.sin(U1)*Math.sin(sigma)*Math.cos(alpha1)));
     C = f/16*cosalpha2*(4+f*(4-3*cosalpha2));
     L = lambda-(1-C)*f*sinalpha*(sigma+C*Math.sin(sigma)*(Math.cos(2*sigmam)+C*Math.cos(sigma)*(-1+2*Math.pow(Math.cos(2*sigmam),2))));
     L2 = L + L1;
     alpha2 = Math.atan(sinalpha/(-1*Math.sin(U1)*Math.sin(sigma)+Math.cos(U1)*Math.cos(sigma)*Math.cos(alpha1)));
     p2 = new google.maps.LatLng(calDegree(phi2),calDegree(L2));
     return p2;
  }

 各距離の円の色が重ならないよう、外側の円のポリゴンはドーナツ状にしています。Google Maps APIのポリゴンでは、内側領域のデータを外側とは逆回りの配列で追記することで中抜けの形を描画することが出来るので、外側の円の描画時にはその1つ内側のポリゴン配列を逆回りにしてPolygonクラスにデータを渡しています(上記ソース125~129行目)。

 そして、上のHTMLを開いて描画させた結果が以下の図です。

計算・描画結果

計算・描画結果 ボツワナの首都(ハボロネ)から1000km,2000km,3000kmの同心円


 
 同心円の色も重ならず、また半透明なので下の地図が見えなくなってしまうこともなくバッチリです。
 「これで完成!」と思ったのですが・・・・・・。

 距離を10000kmなど大きくして試してみると・・・

計算・描画結果(長距離)

計算・描画結果 ボツワナの首都(ハボロネ)から3000km,5000km,10000kmの同心円

 このように一番外側の距離円(もはや円ではありませんが)のポリゴンが本来とは逆の領域を選択してしまっています。その関係で内側の円には外側の円の色(今回の場合は赤)が重なってしまいました。
 中心点や距離をいろいろ変えて描画させてみたところ、どうやらGoogle Maps APIのポリゴンは地球を1周して南北に2分割するような大きな領域を描く際には北半球側を選択するようです。

 ところが悔しいことに、google.maps.Circleで同条件の同心円を描いた場合は以下の図のように南半球でもきちんと選択されています

google.maps.Circle

同条件でにてgoogle.maps.Circleを利用した結果


 「色が重なる程度の小さなことは気にせず、おとなしくgoogle.maps.Circleを使いなさい!」ということなんでしょうか?・・・いえいえ、まだ諦めません。次回へつづく

 久々に本職のシステム関連の投稿を・・・。

 ある日、配属先の同僚から次のような相談を受けました。
「今、ここに首都(ハボロネ)からの距離が500km、1000km、1500kmの同心円になっている地図があるんだけど、この中心をハボロネに限らず任意の点から作図出来るようなアプリ作れない?」
 地図上の距離に関しては以前に「緯度・経度情報のある2点間の距離」を計算する関数を作成しているので「ああ、出来るよ!」と気軽に答えたものの・・・細部にこだわったこともあり(相手はそこまで求めていないのですが)、結構苦労しました。

 まず、地図に関しては中心とする地点の選びやすさを考慮してGoogle Maps APIを利用することにしました。Google Mapにはgoogle.maps.Circleという便利なクラスがあり、これを使えば簡単に同心円が作れます。なので、円を描くだけならそれでいいのです。
 しかし、見本には各円に色が塗ってある。もちろんCircleクラスでも円を塗りつぶすことが出来ますが、複数の円を描く同心円では色が重なってしまいます。透過度をゼロにして内側から描けば色は重なりませんが、今度は地図が見えなくなってしまい場所が何処だか分からなくなってしまうので・・・。結局自分で計算することにしました。

 「点Aからの距離がrである点の座標(緯度・経度)」を求める計算(点Bは無数に存在)。最初考えたのはAから真東にだけ離れた点(同緯度)と真北にrだけ離れた点(同経度)を最初に求めて、その2点を通る楕円上の点の座標を求める方法です(下のイメージ図)。

点Aからの距離がrである点の座標(緯度・経度)を求める計算手法(1)~イメージ~

点Aからの距離がrである点の座標(緯度・経度)を求める計算手法(1)~イメージ~

 以前作成した2点間の距離を計算する関数を使えば、点Aから緯度1度分離れた点及び経度1度分離れた点の距離が計算出来るので、それを基準に上図の点B・点Cの位置情報を求め、後は三角関数を使用して楕円上の点を計算していくというものです。実際、ネットで調べてみると同じような理論で計算されている方もちらほらといらっしゃるので、これでOKかな?と目論んでいたものの・・・。
 以下のコードが当手法で計算しポリゴンとして地図上に落としたものです。

  var map;
  var mapCenter = new google.maps.LatLng(-23, 23);
  var pointMarker = new google.maps.Marker();
  var polygons = new Array();

  function initialize() {

     var myOptions = {
        zoom: 3,
        center: mapCenter,
        mapTypeControl: true,
        mapTypeId: google.maps.MapTypeId.ROADMAP
     };
     map = new google.maps.Map(document.getElementById("map_canvas"), myOptions);

     google.maps.event.addListener(map, "click", function(clickevent) {
        var myPoint = new google.maps.LatLng(clickevent.latLng.lat(), clickevent.latLng.lng());
        setPointMarker(myPoint);
     });


  }

  function setPointMarker(location, pile) {

    if(!pointMarker.getMap()){
       pointMarker = new google.maps.Marker({
          position: location,
          map: map,
          draggable: true,
       });
    }else{
       pointMarker.setPosition(location);
    }
  }

  function drawCircle() {

     if (!pointMarker.getMap()) {
        alert("中心となる点にマーカーを設置して下さい。");
        return 0;
     }

     var myPoint = pointMarker.getPosition();
     var pN = new google.maps.LatLng(myPoint.lat()-0.5,myPoint.lng()); //中心点から北へ0.5度
     var pS = new google.maps.LatLng(myPoint.lat()+0.5,myPoint.lng()); //中心点から南へ0.5度
     var pW = new google.maps.LatLng(myPoint.lat(),myPoint.lng()-0.5); //中心点から西へ0.5度
     var pE = new google.maps.LatLng(myPoint.lat(),myPoint.lng()+0.5); //中心点から東へ0.5度
     var dLat = getDistance(pN,pS); //緯度1度分の距離
     var dLng = getDistance(pW,pE); //距離1度分の距離
     var curLat,curLng;
     var cLat,cLng;
     var polyCircle = new Array;
     var r = 3000000;//今回は3000kmで計算
     var degpitch = 0.5; //今回は同心円上の点は0.5度ピッチで計算

     for (var theta = 0; theta < 360; theta += degpitch) {
        curLng = myPoint.lng() + (r / dLng) *  Math.cos(calRadian(theta));  //経度(X座標)
        curLat = myPoint.lat() + (r / dLat) *  Math.sin(calRadian(theta));  //緯度(Y座標)
        polyCircle.push(new google.maps.LatLng(curLat,curLng));
     }
     polyCircle.push(polyCircle[0]);

     var myCircle = new google.maps.Polygon({
        paths: polyCircle,
        fillColor: '#00FFFF',
        fillOpacity: 0.5,
        strokeColor: '#00FFFF',
        strokeOpacity: 1.0,
        strokeWeight: 1
     });     
     myCircle.setMap(map);

     //比較用 Google Maps API Circleクラスでの描画
     var myCircleB = new google.maps.Circle({
        map: map,
        center: myPoint,
        radius: r,
        fillOpacity: 0,
        strokeColor: "#FF0000",
        strokeOpacity: 1,
        strokeWeight: 2
     });

  }

  //度数→ラジアン変換
  function calRadian(deg){
     return deg * Math.PI / 180.0;
  }

  //2点間の距離を計算(GRS80)
  function getDistance(p1, p2) {
     var a = 6378137.0;             //長半径
     var f = 1.0 / 298.257222101;   //扁平率
     var b = a * (1 - f);           //短半径
     var e2 = (Math.pow(a, 2) - Math.pow(b, 2)) / Math.pow(a, 2);
     var dy = calRadian(p1.lat() - p2.lat());
     var dx = calRadian(p1.lng() - p2.lng());
     var my = calRadian((p1.lat() + p2.lat()) / 2.0);
     var w = Math.sqrt(1.0 - e2 * Math.pow(Math.sin(my), 2));
     var m = (a * (1 - e2)) / Math.pow(w, 3);
     var n = a / w;

     return Math.sqrt(Math.pow(dy * m, 2) + Math.pow(dx * n * Math.cos(my), 2));
  }

 一見良さげなのですが、Circleクラスで描いた円(下図の赤い線)と比較すると結構ズレがあります。
 真東・真西の位置はぴったり合っているようなのですが、特に南東・南西方向のズレは大きく100km以上です(下の拡大図参照)。これだと都市地図等の近距離なら気にならない程度の誤差で収まるかもしれませんが、世界地図レベルとなると実用には耐えられそうもありません。

計算・描画結果

計算・描画結果 ボツワナの首都(ハボロネ)から3000kmの円
Google Maps APIのCircleクラス利用で描いたもの(赤い線)とは結構ズレがあります。
この地図を別窓で表示


誤差の大きい部分を拡大(中心から南東方向)

ズレの大きい部分を拡大(中心から南東方向)。100km以上の誤差があります。これではちょっと・・・


 考えてみたら、経度1度当たりの距離は高緯度になるほど短くなるのに、それを考慮せずに1度当たりの距離を過大評価してしまっている状態ですから、等距離の円も高緯度では実際よりも萎んだ形となってしまうのも当然です。
 また、地球は真球ではなく楕円体なので同じ「緯度1度分」であっても北へ1度と南へ1度とでは距離が違うのですね。つまり、緯度差と距離は比例しないので比率換算では緯度は求められない(上記の方法はそこが間違い)。
 参考までに、上記の2点間の距離を計算する関数を利用して、東京から北へ10度離れた点と南へ10度離れた点までの距離を調べてみたところ、

 ・東京(35.69,139.69)から北へ1度離れた点までの距離:1110479.2m
 ・東京(35.69,139.69)から南へ1度離れた点までの距離:1108641.5m

 2km近い距離差となりました。意外だったのは北側の方が長かったことです。地球は赤道半径の方が極半径より長いので、「低緯度側(北半球の場合は南側)の方が当然長くなるだろうと計算前には思っていたのですが結果は逆でした。プログラムが間違っているのかとも思いましたが、国土地理院のサイトでの計算でもほぼ同じ結果を得られたので間違いないようです。

 緯度は地球の中心からの角度で決めたもの(地心緯度)ではなく、楕円体面の法線と赤道の角度で決めたもの(地理緯度)だそうで、そのため高緯度ほど緯度1度当たりの距離が長くなるようで・・・、私にはなんだか難しいですが、いずれにしても上記の方法では駄目なので、考え直しが必要です。次回へつづく

 プラ(Pula)とはボツワナの現地語(ツワナ語)で雨のことです。
 ケッペンの気候区分によればボツワナの国土の大半はステップ気候に属しており(一部は砂漠気候)、雨が少なく作物が育ちにくい環境にあります。

ケッペンの気候区分(ボツワナ)

ケッペンの気候区分(ボツワナ)


 それだけに、たまに降る雨(プラ)はとても貴重で天からの贈り物。通貨がプラであることや、国旗のデザインに水色が採用されていることからも、ボツワナの人々にとって雨がいかに大切であるかが分ります。
100プラ紙幣

通貨もプラ。上写真は100プラ紙幣(約1,000円)


ボツワナ国旗

ボツワナ国旗。水色は雨(水)を表します。

 10月から4月くらいまでが雨季に当たりますが、首都ハボロネの年間降水量は400㎜程度と東京の4分の1ほどで、雨季といってもそんなに降るわけではありません(だからステップ気候なわけですが)。
 しかし、今シーズンは雨が多いです。ラニーニャの影響だそうですが、特にこの2ヶ月くらいは雨が降らない日の方が多いです。昨年は干ばつで農業が大打撃を受けたようなので、こちらの人々は「今年は雨が多くていいね!」と言っていますが、もともと雨が少ない国なので排水の設備が整っておらず、各所で水害が発生しています。
 私は自転車通勤なのでレインコート必携ですが、雨のおかげでさほど暑くならないのは助かります。昨年の夏は記録的な暑さで気温46度の日も何日かあったそうなので・・・。

ここ最近は雨ばかり

ここ最近は雨ばかり

 ボツワナは「黄熱に感染する危険のある国」には含まれていないので、日本-ボツワナ間の往復にはイエローカード(黄熱予防接種証明書)は不要です。ただ、アフリカには予防接種推奨国がいくつもあるので、今後それらの国へ渡航することを考えると、イエローカードを取得しておいた方が無難です。ボツワナでも黄熱の予防接種を受けられるとの話を聞いて、休暇期間中に受けてきました。

 黄熱予防接種を実施している病院は多くないので(日本も同様ですが)、教えてもらった情報を頼りにまずはハボロネ中心部にあるPrincess Marina Hospitalへ。でも、大きな病院でどこへ行ったらいいのか良く分かりません・・・いくつか尋ね回った末に案内されたのは何故か会計窓口。ここでは黄熱の予防接種は受けられないようで、料金(28.2プラ=約300円)だけを支払い。この病院はワクチンの管理をしている受付センターのような所なのかシステムが良く分かりません。どこなら受けられるのかを聞いたら「Village Clinicが近いよ」とのことなので、そのVillage Clinicへ。
 コンビ(ミニバス)を利用すれば早いのですが、路線が良く分からないのでPrincess Marina Hospitalから歩きました(徒歩30分程)。Village Clinicには10時くらいに到着しましたが、黄熱予防接種は14時からとのこと。その間、他の場所へ行ったりして時間を潰しました。
 そして14時にVillage Clinicを再訪すると、既に結構な人数の来訪者。この時間帯は「黄熱予防接種アワー」のようで全員が私と同じ目的での来院でした。1時間くらい待ちましたが外国人の私でも問題なくワクチン接種・イエローカード取得出来ました。

 受付1箇所で受けられないので時間がかかって面倒ですが、日本なら1万円以上する黄熱の予防接種を300円程度で受けられるというのは破格の安さです。

Princess Marina Hospital

Princess Marina Hospital
病院が混むのは日本だけではないようで、患者さんで溢れかえるような様子でした。

黄熱予防接種の領収書

料金は28.2プラ(約300円)。破格の安さです

Village Clinic

Village Clinic
病院というより保健所といった雰囲気

Village Clinic

取得したイエローカード。昨年から1回接種すれば生涯有効となりましたが、何故か有効期限10年(変更前の期間)になっています。多分、変更されたことを担当者が知らないのだと思います。のんびりした国ですから・・・

 ボツワナを含む南部アフリカ地域の伝統的食材の1つとして「モパネワーム(Mopane Worm)」があります。モパネという木に多くいることから名づけられたそうで、蛾の幼虫です。通称はパニ(Pani)、多分モパネの「パネ」が訛ったものではないかと思われます。

 採ってきた幼虫の内臓をとって乾燥させると数ヶ月は保存が効くので、古くから貴重なタンパク源として、ごく当たり前のフードとして(決してゲテモノではなく)食されています。とはいっても、現代では普通に肉類が流通していますので、一部地域を除けば日常的な食材ではなく、「季節もの」の位置づけです。
 今(1月)がちょうどその季節で、スーパーには売っていませんが(今の所、まだ見かけていません)、露店等で手に入るようです。

 日本でも長野県の一部地域ではザザムシ(オケラの幼虫)を食べる習慣が残っているようですが、イモムシを食べるというのは少なからず抵抗があります。さらに私の場合は、イモムシの見た目が苦手でニョッキですらアウトなくらいなので、かなりハードルの高い食べ物です。でも、「ボツワナに赴任した以上、一度は試さなければ!」と半ば義務のように感じていました。

 そして先日、同僚が入手してきてくれたのでチャレンジ! 目を瞑って食べました・・・味は煮干しに似ています。ただ、妙な後味が残るのが気になって仕方がありません。煮干しだって後味が残りますが気にならないのですから、これは気持ちの問題なんだろうと思います。

 こちらの人は、これをそのままスナック的感覚で食べる他、水に戻してトマトソースで煮るなどの料理をして食することもあるそうなのですが、そちらの方は遠慮しておきます。私には無理です。一応、ミッションクリアということで・・・。

パニ

パニ