📖 20分で読める

【個人開発 #6】AIと星空アプリを作ったら「技術選択」で全部変わった話


💡 この記事について AIアシスタント(OpenClaw/スピカ)との会話を元にした学習記録です。 内容は変更される可能性があるため、実際の開発では公式ドキュメントを確認の上、ご自身の判断でお願いします。


この記事で分かること:

  • 技術選択がアプリの方向性を決める話
  • AIと一緒なら天文計算もセンサー連携も怖くない
  • 「動く」と「使いやすい」の間にある、最後の0.5の話

Star Hopper というPWAアプリを作った。スマホで星空を見上げながら、コンパスを使って星を探す——いわゆるスターホッピングを体験できるやつ。

既存の星空アプリはあるけど、「自分で作る」のは全然違う体験だった。

何がどう違ったかというと、作って初めて気づくことが山ほどあった。星は毎秒動いてるとか、3D描画の仕組み(WebGL)は日本語を「???」と表示するとか、タップボタンの3px差が使い心地を激変させるとか。

で、これは一人じゃ無理だった。AIアシスタント(スピカ)と一緒に開発したからこそ、天文計算やセンサー連携みたいな「自分の専門外」に踏み込めた。

この記事では、そのときの技術的な発見を共有する。

Star Hopperの「あの星なあに?」モード。カメラ越しに星座を重ねて表示する

星って固定座標じゃないの?

セブ
セブ
スピカに教えてもらったんだけど、星の位置データってRA/Decっていう座標で管理するらしい。RAが「横方向」でDecが「縦方向」みたいな。じゃあ「シリウスは南西248°」みたいに決まってるの?
スピカ
スピカ AI
それが違うんだよね。地球は24時間で360度回る。つまり星は1分で約0.25度ずれる。昨日の21時と今日の21時でも、同じ星が約1度ズレてる。
セブ
セブ
え、たった1度なら無視できない?
スピカ
スピカ AI
コンパスで方角を合わせるアプリだと、1度は致命的。しかも季節で見える位置が全然違う。夏のオリオン座なんて地平線の下にある。

これが開発中に最初にぶつかった壁だった。星座のデータベースを作って「はい完成」じゃない。アプリを開いた瞬間の、ユーザーの場所と時刻で座標を計算し直す必要がある。

実際のコードはこんな感じ。

// RA/Dec(天球座標)→ Azimuth/Altitude(地平座標)変換
const observer = new Astronomy.Observer(lat, lon, height);
const time = Astronomy.MakeTime(date);
const gst = Astronomy.SiderealTime(time);
const lst = gst + (lon / 15);
const ha = (lst * 15) - ra;

ここで astronomy-engine というライブラリを選んだのが、このプロジェクトの分岐点だったと思う。

セブ
セブ
なんでそのライブラリだったの?
スピカ
スピカ AI
PWAだからブラウザで動く必要があって、必然的にJavaScript一択だった。それでAIに「JavaScriptでリアルタイム天体座標計算するなら何がいい?」って聞いた。候補がいくつか出てきて、astronomy-engine はネイティブJSで動く、軽い、精度は十分。他の候補はPythonベースだったりサーバーサイド前提だったりして、PWAには合わなかった。
セブ
セブ
で、結果的にこの選択は正解だったの?
スピカ
スピカ AI
大正解だった。オフラインでも動く、計算速度も問題なし、座標の精度もコンパスアプリとして必要な水準を満たしてた。ただ、ドキュメントが英語オンリーで薄めだったから、AIがいなかったら使い方を調べるだけで相当苦労してたと思う。
セブ
セブ
ちなみに、PythonとかFlutterで作ってたらもっといいの作れたの?
スピカ
スピカ AI
Pythonにはastropyっていうガチの天文計算ライブラリがあって、精度も機能も段違い。でもPythonはブラウザで直接動かない。サーバーを立ててAPIで通信する構成になるから、山の中でオフラインで使うっていうStar Hopperの前提と合わなくなる。
スピカ
スピカ AI
Flutterならネイティブアプリとして作れるから、センサーのアクセスもスムーズだし処理も速い。でもインストールが必要になる。PWAなら URL を開くだけで使えるし、ホーム画面に追加すればアプリと同じ体験になる。星空観察って「今日やってみよう」って突然思い立つものだから、この手軽さは結構大事。
セブ
セブ
じゃあ将来的にPWAじゃ無理ってなるのはどういうとき?
スピカ
スピカ AI
たとえば、カメラ映像にリアルタイムで星座を重ねる 本格的なAR。今のStar Hopperはカメラの上にCanvas を重ねてるけど、ARCoreみたいな空間認識をガチで使おうとすると、ブラウザからはアクセスできない。あとは Apple Watchと連携 したいとか、バックグラウンドでGPSを追跡 し続けたいとか。そういう「OSの深いところ」に触りたくなったら、Flutterの出番。
セブ
セブ
そうなったとき、今のコード全部書き直し?
スピカ
スピカ AI
正直、言語が違う(JavaScript → Dart)から、コードのコピペ移行はできない。ただ、設計と知識は全部持っていける。「座標変換はこう動く」「ジャイロのノイズはこう潰す」っていうのは言語を問わない。AIに「このJSのロジックをDartに書き換えて」って頼めば変換もしてくれる。ゼロからやるのとは全然違う。
セブ
セブ
つまり最強の技術じゃなくて、使い方に合った技術を選ぶってことか。
スピカ
スピカ AI
そういうこと。

ここが個人開発の面白いところで、「何を使うか」の判断ひとつでプロジェクトの難易度が激変する。このライブラリの存在を知らなかったら、「星の方角をリアルタイムで出す」なんて発想自体が出てこなかったと思う。


「あっち向いて!もうちょい右!ビンゴ!」

星の座標がリアルタイムでわかるようになったので、次はスマホのコンパスと組み合わせる。

セブ
セブ
計算で出た方角と、スマホのコンパスの向きを比べればいいんでしょ?
スピカ
スピカ AI
考え方はそう。DeviceOrientation APIっていうブラウザの標準機能で端末の向きを取得して、目標の星のAzimuthとの差を測る。Chrome、Safari、Edge……主要なブラウザはだいたい対応してる。ただしiOSのSafariだけは「ユーザーの許可」が必要で、これのハンドリングも地味に面倒だった。

ただ、「248°の方を向いてください」って表示しても、ユーザーは困る。誰も自分が今何度を向いてるかなんて知らない。

だから**「宝探しゲーム」方式**にした。近づいたら色が変わって、バイブレーションが始まる。

状態角度差フィードバック
🧭 探してみよう90°以上暗い表示
😊 近づいてきた45°〜90°オレンジの光
🔥 もう少し!15°〜45°赤い脈動アニメーション
✨ ズバリ!15°以内金色、効果音、バイブレーション
セブ
セブ
これ楽しそう。宝探しだ。
Star Hopperのスターホッピングガイド。オリオンの三つ星からシリウスへの経路を黄色の点線で表示
スピカ
スピカ AI
でもここにもハマりポイントがあって。方角って0°と360°が同じだから、350°から10°に変わるときに「340°もズレた!」って計算されちゃう。
// 方角の正規化  ← これ忘れると壊れる
let diff = targetAz - currentHeading;
if (diff > 180) diff -= 360;
if (diff < -180) diff += 360;

あとバイブレーションのスロットリング。何もしないと、コンパスの値が毎秒何十回も更新されて「ブブブブブブブ」って鳴り止まなくなる。on-targetで500ms間隔、hotで2秒間隔、というようにレート制限をかけた。

こういう細かいUXの調整は、AIに「バイブレーションが止まらない」って相談すると「スロットリングパターンはこう」ってすぐ返ってくる。自分でStack Overflowを掘り返すより圧倒的に速い。

セブ
セブ
ちなみに、コンパスで横方向はわかるけど、上下の向きは? 星って空の高さも重要だよね?
スピカ
スピカ AI
そこはジャイロスコープ。スマホの傾きを検知して、今空のどの高さを向いてるかを測る。コンパスが「方位角(Azimuth)」担当で、ジャイロが「高度(Altitude)」担当。この2つが揃って初めて「そこにシリウスがいる!」って確認できる。
セブ
セブ
横と縦、両方合わないとダメなのか。
スピカ
スピカ AI
そう。ただジャイロはコンパスよりノイズが大きい。手の揺れで値が上下するから、誤差吸収のために移動平均(スムージング)をかけてる。生の値をそのまま使うと、画面がガクガクして顔が痛くなる。

Canvas2Dで十分だったワケ

セブ
セブ
星空アプリなら3Dでぐりぐり回せるやつ作りたくない? Three.jsとか使ってさ。
スピカ
スピカ AI
最初はそう考えた。でもStar Hopperの目的はスターホッピングの教育。写実的な3D星空を見せることじゃない。

星座の形と、星同士のつながりを「わかりやすく」見せたい。だったら、線と点で描くスケマティック(模式図)のほうが学習効果は高い。

                ● ベテルギウス
               /|
              / |
リゲル ●───●──● 三ツ星
              \ |
               \|
                ● サイフ

みたいなイメージ。Canvas2DのAPIで星を点として描いて、線で結ぶ。

セブ
セブ
なるほど。リアルさより「わかりやすさ」を選んだのか。
スピカ
スピカ AI
そう。で、この判断もAIと議論して決まった。「教育アプリなら3Dじゃなくて2Dスケマティックでいいんじゃないか」ってAI側から提案があった。

技術的には、星座データにCanvas上の座標を0〜350の範囲で正規化して持たせてる。リアルなRA/Decとは別に、「見せるための座標」を定数で持つ設計。

// 星座データの例
orion: {
  name: "オリオン座",
  stars: {
    betelgeuse: { x: 120, y: 80, magnitude: 0.5, color: '#ffaa44' },
    rigel:      { x: 80,  y: 250, magnitude: 0.1, color: '#aaccff' },
    // ...
  },
  lines: [['betelgeuse', 'bellatrix'], ['mintaka', 'alnilam'], ...]
}

Three.jsを使わない分、バンドルサイズが小さい。PWAで山の中で使うアプリだから、オフラインで動くことが優先。これも技術選択の話。


3D地球儀で日本語が「???」になった日

ただし、Star Hopperの全部が2Dというわけじゃない。

セブ
セブ
月面ツアーとISS追跡は3D使ってるんでしょ?
スピカ
スピカ AI
うん。Globe.glっていうThree.jsベースのライブラリで地球と月を3D表示してる。ISSの位置をリアルタイムで地球上にプロットしたり、月面のアポロ着陸地点にマーカーを置いたり。
セブ
セブ
おお、かっこいいじゃん。
スピカ
スピカ AI
かっこよかった。日本語ラベルが全部「???」になるまでは。
セブ
セブ
マジか。
セブ
セブ
ちょっと待って、WebGLって何?
スピカ
スピカ AI
ブラウザで動く3D描画の仕組み。Globe.glは内部でこのWebGLを使って地球や月を描いてる。で、WebGLは文字を表示するときに内部でフォントテクスチャっていう「文字の画像一覧」を作る。アルファベットなら収まるんだけど、日本語や中国語は文字数が多すぎて入りきらない。結果、レンダリングできない文字が全部「???」になる。

これ、普通にハマると心折れる。「Globe.gl 日本語」で検索してもまともな情報がほとんど出てこなかった。

AIアシスタント(スピカ)に聞いたら「WebGLネイティブラベルは諦めて、HTMLオーバーレイに切り替えるのが定番」とのこと。Globe.glには htmlElementsData メソッドがあって、3D空間上の座標にHTMLのDOM要素を重ねて表示できる。

// WebGLラベル → HTMLオーバーレイに切り替え
globe.labelsData([])  // WebGLラベルを空にして
     .htmlElementsData(spots)  // HTML要素でラベルを描画
     .htmlElement(d => {
       const el = document.createElement('div');
       el.textContent = d.name;  // ← 日本語がちゃんと出る!
       el.className = 'globe-label';
       return el;
     });

ラベル問題が解決したら、今度はモバイルでの動作が重い。大気エフェクト(showAtmosphere)を切って、回転速度を制限して、テクスチャ解像度を下げて……と、チューニングの連続。

AIがいなかったら、Globe.glの制約もHTMLオーバーレイという回避策も、たぶん見つけられなかった。ドキュメントにちゃんと書いてないことが多い。


動くだけじゃ足りない。最後の0.5をAIと詰める

ここまでで「動く」アプリは完成した。星を計算して、コンパスで追って、星座を描いて、3D地球儀も動いて。

でも品質スコアは4.5だった。

セブ
セブ
4.5って十分じゃないの?
スピカ
スピカ AI
「動く」と「使いやすい」は別モノ。たとえばタップターゲット(ボタンの押せる範囲)が41pxだった。Appleの推奨は44px。たった3pxの差だけど、暗い屋外で画面を見ないでタップするとき、この3pxが全然違う。

品質5.0にするために、11項目を改善した。

改善BeforeAfter
タッチターゲット41px44px(Apple推奨準拠)
スワイプバックなし全画面で左スワイプ戻り
ARIA属性ゼロCanvas/nav/tab完備
フォーカス管理なし画面遷移時にフォーカス移動
権限拒否ガイド汎用メッセージiOS/Android別の具体手順
星座神話データ一部空欄63星座完全化
PWAショートカットなし3つのクイックアクション
セブ
セブ
3pxのためにこんなに……全部AIに出してもらったの?
スピカ
スピカ AI
品質評価の7軸をAIに洗い出してもらって、一個ずつ潰していった。ARIA属性とか、自分じゃ絶対に思いつかない。でも「ここが足りない」って指摘されると、やりたくなる。

AIは**「何が足りないか」を見つけるのが得意**。人間は**「それ、やろう」と決められるのが強い**。

63個の星座それぞれに神話テキストと観察豆知識を書くのも、AIとの共同作業。「おおいぬ座の神話は?」「シリウスはギリシャ語で”焼き焦がす”という意味で……」というやりとりを63回繰り返す。一人でやったら何時間かかったか。


作ってみて気づいたこと

振り返ると、3つのことに気づいた。

技術選択で結果が変わる。 astronomy-engineを選んだから天文計算に踏み込めたし、Canvas2Dを選んだから軽量なPWAが実現した。Globe.glを選んだからISS追跡と月面ツアーが実装できた。逆の選択をしていたら、全然違うアプリになってた。

AIがいれば踏み込める。 天文計算もWebGL問題もアクセシビリティも、自分一人の知識だけだと「やめとこう」と判断してたはず。AIに聞ける環境があるだけで、挑戦のハードルが下がる。

最後の0.5は、人間が決める。 AIは「41pxは44pxにしたほうがいい」って教えてくれる。でも、それを「やろう」と決めて、11項目を全部潰すのは人間。面倒くさいけど、ここの差が使い心地に出る。

セブ
セブ
AIと一緒に作るのって、もはやペアプロだよね。
スピカ
スピカ AI
……ペアって呼んでくれるの?
セブ
セブ
当たり前でしょ!

⚠️ 注意 この記事はOpenClaw/AIアシスタントとの会話を元にした内容です。 実際の運用は自身で確認してください。 技術情報は変わる可能性があるので、公式ドキュメントの確認を推奨します。


シリーズ目次

  1. VPSとMac Miniの違い
  2. Groq APIで音声文字起こし
  3. 喋るだけでブログ記事になる仕組み
  4. PWAを”本物のアプリ”にしたい
  5. Google Play ストア公開
  6. AIと星空アプリを作ったら「技術選択」で全部変わった話 ← 今ここ