「観光客の運転が荒いんじゃなくて、沖縄の道が分かりにくいのでは?」

どうも、レイと申します。

お盆前の沖縄、暑さが一段階上がった気がします。前回の記事で「日差しが下からも来る」と書いたんですが、8月に入ってからはアスファルトの上に空気の層が見えるレベル。陽炎ってやつですね。あれ、内地だと真夏の高速道路とかで見るやつだと思うんですけど、こっちだと普通に市街地の道路で見えます笑

そんな中でも観光客の方はレンタカーで元気に走り回っていて、毎年のことながらすごいなと思ってます(^o^)


さて。前回・前々回と2回連続で「次はちゅらPIN!の紹介を書きます」と予告しておきながら、2回とも別の記事を挟んだ男がこちらです(´・ω・`)

3度目の正直、ようやく本題です。沖縄ドライブの口コミマップ「ちゅらPIN!」の紹介をします。

ℹ️ この記事の読み方

前半はサービスの紹介と使い方です。後半に「技術編」として、県警のPDFを地図データにするまでの開発の話をまとめてあります。

技術の話に興味がない方は、技術編はまるっと飛ばして「今後の予定」まで進んでOKです。逆に技術目当てで来た方は、目次から技術編に飛んでください(^o^)


ちゅらPIN! ってどんなサービス?

一言でいうと、沖縄をドライブする人向けの、地図型の口コミアプリです。

ちゅらPIN! のマップ画面。国道58号と国際通りにバスレーンの規制ラインが表示されている地図の上に、性格の違う2種類の情報が乗っています。

1つめが「公式情報レイヤー」。行政が出しているデータをもとに、運営(=僕)が載せている情報です。バス専用レーンの規制区間と、米軍施設の境界線。ここは投票の対象外で、常に表示されます。地図の信頼できる土台という位置づけです。

2つめが「みんなのPIN!」。ユーザーが地図上の好きな場所に立てられる口コミです。「この交差点、右折レーンが手前すぎて毎回ヒヤッとする」みたいな、ガイドブックには絶対に載らないやつ。こっちがサービスの本体です。

そしてインストールも会員登録も要りません。ブラウザで開けばそのまま使えます(PWAなので、ホーム画面に追加すればアプリっぽくも使えます)。

旅行者に「まずアプリを入れてアカウントを作ってください」って言っても、まあ入れてもらえないですよね。そこは最初から諦めました笑


そもそも、沖縄の道の何が分かりにくいのか

「作ったきっかけ」の話でもあるので、先にこれを書かせてください。

沖縄あるあるとして、「観光客のレンタカーは運転が荒い」みたいな言われ方をすることがあります。

僕、これがずっと引っかかってたんですよ。

だって、旅行中にわざわざ荒い運転をしたい人なんていないはずじゃないですか。事故ったら旅行が終わるので。

じゃあ何が起きてるのかというと、たぶん**沖縄の道が単純に分かりにくい**んですよね。しかも「分かりにくい」の中身が、内地の道路にはない種類のものだったりします。

  • 時間帯によって走っていい車線が変わる(バス専用レーン)
  • 曲がった先が普通に米軍基地のゲートだったりする
  • 道路が基地をよけて作られているので、地図上の直線距離と実際のルートが噛み合わない

これ、地元民は体で覚えてるだけなんです。標識を読んで判断してるわけじゃなくて、「この時間のこの道は左車線に入らない」を反射でやってる。

知らない人が初見で読み取れる情報量じゃないんですよね、正直。

なので「運転が荒い」んじゃなくて、「知らないルールの上を走らされている」んじゃないか、と。

じゃあその「地元民が体で覚えてる部分」を地図に置いておけば、少しは違うんじゃないの——というのがちゅらPIN!の出発点です。

💡 なぜ「口コミ」の形にしたか

最初は公式の規制情報だけを載せた地図にしようと思ってました。

でも、地元民が本当に知ってる情報って、公式データには一切載ってないんですよね。「あの坂、雨の日めっちゃ滑る」とか「ここのコンビニ、出るとき右折できない」とか。

そういうのは行政のデータには絶対に出てこないので、人から集めるしかない。だから口コミがメインで、公式情報はその土台、という設計にしました。


地図の見方

ここからは実際の画面の話です。

バスレーンは「青」と「赤」で意味が違う

まずの線。これ、色が違うだけじゃなくてルールが全然違います

  • 青 = バス専用レーン … 優先車線。実は他の車も走れます
  • 赤 = バス専用道路 … 一般車は進入禁止。入ったらアウト

国際通りの朝夕がまさに後者です。反則金6,000〜7,000円+減点。

……というのを、僕もこのアプリを作る過程で正確に理解しました。地元民でもこれです。旅行者に分かれというのは無理があると思いませんか?www

線に付いている矢印は規制対象の進行方向です。反対方向は規制されていないので、そこも見分けられるようにしてあります。

時間帯によって、色が変わります

バスレーンの厄介なところは、平日の朝(07:30〜09:00)と夕方(17:30〜19:00)だけ有効で、それ以外の時間は普通に走れることです。つまり同じ道が、時間によって意味を変える

なので、いまが規制中かどうかで表示を変えています。

平日朝8時のちゅらPIN!。バスレーンが青と赤で発色し、上部に「朝 規制中」と表示されている 規制時間外のちゅらPIN!。同じ区間がグレーで表示され、上部に「規制外」と表示されている上が平日の朝8時、下が規制時間外です。規制中は発色、規制時間外はグレー。同じ場所ですが、色が変わっているのが分かるかと思います。

画面上部のバッジも「朝 規制中」「規制外」で切り替わります。地図って情報量が多いので、結論は文字で書いてあげたほうが親切だなと思って付けました。

最初は「規制中だけ線を出す」つもりでした

そのほうがシンプルだし、間違えようがないと思ったんですよ。

でも作ってみたら、これが逆に不安になるUIでした。

昼間に開くと地図に何も出ていない。これ、「規制がない」のか「アプリが壊れてる」のかユーザーには区別がつかないんですよね。しかも「この道、夕方は規制されるんだっけ?」を事前に知りたい人には、情報がゼロになってしまう。

なので「グレーで出しておく」に落ち着きました。地図が賑やかすぎると感じる人向けに、「全部表示 / 規制中のみ / 非表示」の切り替えも用意してあります。

バッジを押すと、規制時間と種別の詳細が開きます。

バスレーン規制の詳細パネル。朝07:30〜09:00と夕17:30〜19:00の時間帯、マップ表示の切り替え、反則金の注意が表示されている### 米軍施設の境界線

もうひとつの公式情報レイヤーが、米軍施設の境界です。嘉手納・普天間・キャンプ各基地などをピンク色の枠で表示していて、タップすると施設名が出ます。

沖縄に来たことがない方には想像しにくいと思うんですが、普通に走ってると基地の入口に着いてしまうことがあるんですよ。カーナビが最短ルートとして案内してくることもある。

ちゅらPIN! の地図凡例。米軍施設境界、バスレーン規制の時間帯と種別、PIN!の公開エリアが説明されている凡例はいつでも開けるようにしてあります。上のスクショがそれです。

⚠️ 境界線は「目安」です

米軍施設の境界は公開地理情報(OpenStreetMap)から作っています。近似だったり、未収録の区域があったりします

アプリにも同じ注意書きを出していますが、**現地の標識・フェンス・看板が絶対**です。地図を信じて突っ込まないでください。

みんなのPIN!(口コミ)

そしてサービスの本体、口コミです。

投稿は地図をタップして、カテゴリと本文を書くだけ。カテゴリは「危険・注意 / 道路状況 / 渋滞 / その他」の4つ。ハンドルネームは任意なので、名乗らなくても投げられます。

PIN! は投稿者の立場で色分けされます。

種別中身
🟦 青公式情報運営が登録。投票なし
🟩 緑地元民の口コミ沖縄在住者からの注意喚起
🟨 黄観光客の口コミ旅行者の体験談・気付き

なんで分けているかというと、同じ情報でも、誰が言っているかで受け取り方が変わるからです。「毎年ここで詰まる」は地元民の言葉として読みたいし、「初見だとこの分岐で迷う」は旅行者の言葉だからこそ価値がある。どっちが上ということではないです。

情報は投票で育てる

口コミサービスで一番怖いのは、古い情報と間違った情報が残り続けることです。工事はいつか終わるし、道路も変わる。

なので各PIN!には投票が付いています。

  • 👍 役に立った … スコアが上がって、地図上で目立つようになる
  • 👎 違う・古い … スコアが下がって、最終的に埋もれていく
  • 🚩 通報3件たまると自動でフィードから消えます

投稿自体は承認待ちなしで即時表示です。「投稿したのに出てこない」が一番モチベーションを削ぐので、そこは待たせない設計にしました。荒れたときは投票と通報で沈む、という順番です。

表示エリアは段階的に広げていきます

投稿はどこからでもできるんですが、表示のほうは投稿数に応じて段階的に開放しています。

ステップ目安開放エリア
Step 150件〜那覇市内(国際通り・58号周辺)
Step 2100件〜中部(北谷・宜野湾・沖縄市)
Step 3150件〜全島

理由は、広いエリアに数件だけポツポツある状態が一番スカスカに見えるからです。狭いエリアに集中してる方が「お、賑わってる」感じが出る。

いまは Step 1、那覇からです。

AIガイドにも聞けます

画面下のタブに「AIガイド」というのがあって、沖縄の運転ルールや周辺のことをチャットで聞けるようになっています。

「バスレーンって何時から?」みたいな質問を、地図を読まずに文章で解決したいときのためのやつです。地図って結局「読める人には速いけど、読めない人にはしんどい」ものなので、逃げ道を用意しておきたかった。

無料枠の範囲で動かしています

Cloudflare の Workers AI の無料枠(1日10,000ニューロン)で動かしていて、使いすぎないように自前で利用回数のクォータも噛ませてあります。

無料枠を突き抜けて請求が来るのが一番怖いので……(´・ω・`)


いまは CLOSED β(合言葉が要ります)

現在はクローズドβで運用中です。地図本体を開くのに合言葉が必要になっています。

合言葉は churapin! です。ポートフォリオのトップにも書いてあるので、隠してるわけではないです笑

なんで一段挟んでるのかというと、口コミがゼロの口コミアプリほど寂しいものはないからです。

いま開いても、僕が置いた公式情報レイヤーはあっても、肝心の口コミはまだスカスカです。その状態で一般公開して「なんだ、何も無いじゃん」で閉じられてしまうと、二度と戻ってきてもらえない。

なので今は種まき期間と割り切って、合言葉を知ってる人にだけ投稿してもらっています。ある程度たまったら合言葉を外す予定です。


【技術編】県警のPDFを地図に載せるまで

ℹ️ ここから開発の話です

サービスの話は上でおしまいです。ここからは「バスレーンの線、どうやって引いたの?」という開発側の話をします。

興味のない方は「今後の予定」まで飛ばしてください(^o^)

「バス専用レーンの区間を地図に描くだけでしょ?」——僕も最初はそう思っていました。1日で終わるだろ、と。

全然そんなことなかったです。

規制データは「PDF」で配られている

まず大前提として、バス専用レーンの規制情報は沖縄県警が出しているPDFです。

PDFです。地図データではなく、PDFです。

つまり、緯度経度なんてどこにも書いてない。書いてあるのはこういう情報です。

国道58号  伊佐 → 仲西   平日 07:30〜09:00  バス専用レーン
国際通り  安里三差路 → 県庁北口  平日 07:30〜09:00  バス専用道路
...
交差点の名前しか書いてない。

人間が読むぶんには完璧な情報なんですよ。「伊佐から仲西まで」って言われたら地元民は分かる。でもプログラムに「伊佐から仲西まで線を引いて」と言っても、伊佐がどこなのか知らないので何もできない。

やることは2つに割れます。

  1. 交差点の名前を座標に変換するジオコーディング
  2. その2点の間の「道路の形」を切り出す

交差点名 → 座標、そして「安里三差路」問題

1つめはOpenStreetMap(世界中の人が作ってるオープンな地図データ。以下OSM)に聞きました。Overpass API というOSM用の検索APIに「この範囲で、名前が『伊佐』の点をくれ」と投げれば座標が返ってきます。

ただ、同じ名前の点が複数返ってくるんですよ。交差点の「伊佐」もあれば、バス停の「伊佐」もある。どっちを採用するかで線の始点が数十メートルずれるので、「交差点だよ」というタグ(junction=yes)が付いている点を優先しました。

で、地味に一番「うわ〜」となったのがこれです。

県警のPDFは「安里三路」、OSMは「安里三路」。

漢字が1文字違う。

完全一致で検索してるので1件もヒットしません。しかもエラーになるわけじゃなく、静かに「見つかりませんでした」で終わる。ログを1行ずつ見るまで気づきませんでした(´・ω・`)

こういうのは**行政データを扱うときの宿命**なんでしょうね。出典が違えば表記も違う。対処は、素直に別名テーブルを持たせました。

// PDF表記とOSM表記の対応
const NAME_ALIASES = {
  安里三差路: ['安里三叉路']
};

泥臭いですが、これ以上きれいなやり方が思いつきませんでした。表記揺れを自動で吸収しようとすると別の誤爆が出そうだったので、見つけたら手で足す方式です。

道路は「1本の線」では手に入らない

2つめ、区間の切り出し。ここが一番の山でした。

素朴に考えると「国道58号の線データを取ってきて、伊佐から仲西までを切ればいい」なんですが、OSMの道路データは1本の長い線として入っていません

実際は細切れの線分(way)が大量にある状態です。交差点で切れてたり、橋の部分で別扱いになってたり。国道58号を範囲指定で取ってくると、数十本のバラバラの線が返ってきます。

なので、こういう手順になりました。

  1. 範囲内の「国道58号」のwayを全部取ってくる
  2. 端っこの座標が一致するもの同士をくっつけて、長い1本の線に育てる
  3. 育った線の上で、始点(伊佐)と終点(仲西)に一番近い場所を探す
  4. その2点の間だけを切り出す

3と4は Turf.js という地理計算ライブラリの nearestPointOnLinelineSlice にやってもらいました。2の「くっつける」は自前で、線分を総当たりで見て端点が同じものを繋ぎ、繋げなくなるまで繰り返すだけです。地味。

さらに、交差点のノードは道路の中心線から微妙にズレて登録されていることがあります。なので「一番近い点にスナップ」する必要があるんですが、それをやると今度は全然関係ない道路にスナップして、とんでもない線が引かれる事故が起きる。

そこで、ズレの合計が一定を超えたら失敗扱いにしました。

// 合計 1.5km 以上ズレてたら失敗扱い
if (best.score > 1.5) return null;
⚠️ この手の「静かに間違う」やつが一番怖い

線を引く処理って、失敗してもエラーにならないんですよ。とりあえず何かしらの線は引けてしまう。

なので「動いた!」と思って地図を見たら、那覇市内のはずが名護あたりまで線が伸びてたりする。目視で気づけたからよかったものの、これ気づかずに公開してたらと思うとゾッとします。

前回の記事で「動くことと正しいことは別」と書いたんですが、地図はそれが特に露骨に出るなと思いました。

最終的に、朝10区間・夕8区間の計18区間すべてを線として書き出せるところまで持っていけました。ここまで来るのに、体感で最初の見積もりの10倍くらいかかってます笑

この処理はビルド時に1回だけ動かしてます

上の一連の処理は、アプリの実行時ではなく手元で1回だけ動かして、結果を GeoJSON(地理データを表現するJSON形式)としてファイルに保存しています。

ユーザーが地図を開くたびにOSMへ問い合わせるのは遅いし、相手のサーバーにも失礼だからです。規制情報は年単位でしか変わらないので、変わったときに僕が手元でコマンドを1回叩けばいい。

npm run data:bus-lanes   # バスレーンGeoJSON再生成

「毎回計算する必要がないものは、先に計算しておく」というのは、前回の記事で書いた静的サイトの考え方とまったく同じですね。

正直に書くと、祝日はまだ見ていません

現時点での限界も白状しておきます。規制中かどうかの判定はこうなっています。

export function currentPeriod(now: Date = new Date()): Period {
  const day = now.getDay(); // 0=日, 6=土
  if (day === 0 || day === 6) return 'inactive';
  const total = now.getHours() * 60 + now.getMinutes();
  if (total >= 7 * 60 + 30 && total < 9 * 60) return 'morning';
  if (total >= 17 * 60 + 30 && total < 19 * 60) return 'evening';
  return 'inactive';
}

見ての通り、土日は除外していますが、祝日は判定していません

実際の規制は土日祝と1/2・1/3が対象外なので、祝日にアプリを開くと「規制中」と出てしまいます。実際より厳しい側に出るので事故にはなりませんが、正しくはない。

日本の祝日って、ハッピーマンデーやら振替休日やら、法律が変わると日付も変わるやらで、自前で持つとメンテが終わらないんですよね。かといって外部APIを叩くと、そのサービスが死んだ日にアプリも道連れになる。どうするのが正解なのか、まだ決めきれていません。

(ご意見あればぜひ教えてください。真面目に悩んでます)

技術スタック

軽く並べておきます。

領域使ったもの
フレームワークSvelteKit 2 + Svelte 5
地図描画MapLibre GL JS + OpenFreeMap
地理計算Turf.js
ホスティングCloudflare Pages
データベースCloudflare D1(SQLite
スタイリングTailwind CSS
テストVitest + Playwright

前回の記事で「個人サイトの維持費はドメイン代だけ」という話を書きましたが、このアプリも丸ごと同じ構成に乗っています。地図タイルも OpenFreeMap という無料のものを使っているので、実質ドメイン代だけで動いてます。

地図アプリって「重そう」「金かかりそう」なイメージがあると思うんですが、個人開発で普通に成立します。ここは声を大にして言いたい。


今後の予定

やりたいことはまだ結構あります。

1. OPEN β 化(合言葉ゲートの撤去)

口コミがある程度たまったら、合言葉を外して誰でも入れるようにします。実装としては環境変数を1個消すだけにしてあるので、判断さえすれば即日でいけます。

2. エリアの段階開放

さっき書いた那覇 → 中部 → 全島のやつです。いまは Step 1。まずは那覇の密度を上げたい。

3. GPSアラート

規制エリアに近づいたら教えてくれる機能。本来これが一番欲しい機能なんですよね。走ってる最中に地図を見るわけにいかないので。

ただ、スマホのブラウザってバックグラウンドでの位置情報の扱いに制限が強くて、「画面を表示したまま使ってもらう」前提で設計しないといけない。ここはまだ悩み中です。

4. 多言語対応(英語・繁体字中国語・韓国語)

そもそもの想定ユーザーに外国人観光客が入っているので、これは必須だと思ってます。というか、日本語でしか読めない交通ルールって、その時点でだいぶ不親切ですよね。


おわりに

まとめます。

  • ちゅらPIN! は、沖縄ドライブの口コミ+公式規制情報を載せた地図アプリ
  • バスレーン(青と赤で意味が違う)と米軍施設境界を、時間帯に応じて地図で確認できる
  • 口コミは誰でも投稿できて、投票で育つ。インストールも登録も不要
  • いまは CLOSED β(合言葉 churapin!)。口コミを集めている真っ最中

作りながらずっと思っていたのは、「知らなかった」で罰金を払う人が減ってほしいということでした。

沖縄の交通ルールって、別に意地悪で複雑になっているわけじゃないんですよ。基地があって、バスがあって、細い道があって、その積み重ねでこうなっている。地元民はそれを体で覚えているだけで、覚えていない人が悪いわけじゃない。

だったら、覚えている人の頭の中を地図に置いておけばいい。それだけの話なんですよね。

もし沖縄に来る予定がある方は、合言葉 churapin! で覗いてみてください。そして「ここ危ないよ」を1個でも置いていってもらえると、めちゃくちゃ助かります。地元民の方はもっと大歓迎です(^o^)

免責事項

本記事および本サービスの内容は、筆者が公開情報をもとに整理したものです。交通規制の情報は変更される場合があり、正確性を保証するものではありません。

実際の運転にあたっては、必ず現地の標識・道路標示・警察官の指示に従ってください。本サービスの情報を参考にした結果について、筆者は一切の責任を負いません。


次回は、Claude Code をいつでもどこでも使えるようにした環境の話でも書こうかなと思ってます。iPad からでもスマホからでも開発できる状態にしてあるんですが、これが思ったより快適で。しかもお金をかけずにできてしまったので、そのへんを。

お盆の沖縄は道が混みます。特に那覇周辺、時間に余裕を持って動きましょう。そして**朝と夕方のバスレーンにはくれぐれもお気をつけて**(^o^)

それでは!次回もよろしくお願いします。