🌐 本文还有其它语言版本:中文English

御坂の個人足跡マップ:要件定義から AI 開発・公開まで

もう10年以上前から、私は Google マイマップを使っていた。きっかけは Google Earth だ。精細で情報量が多く、「自分が行ったことのある場所を探し出して、記録としてピンを立てる」のにぴったりだと感じた。その後、ブログの「About」ページにマイマップのリンクを貼り、自分のブログ上で足跡を見られるようにした。

1
<iframe src="https://www.google.com/maps/d/embed?mid=1FnQqdlXYFoEnO6WvZ8QEHj9RYHg" width="100%" height="600"></iframe>

だが使ううちに不満も出てきた。まず、Google マップは中国本土ではずっとブロックされていて、足跡を更新するたびに VPN が要る。だから更新の頻度はごく低かった。次に、Google の衛星画像と道路・地点などの情報は中国国内では座標がずれていて、見た目がよくない。ある地点を、衛星画像に合わせて打つのか、街路の位置に合わせて打つのか、いつも迷ってしまう。ここ数年は、百度地図や高徳地図(Amap)のお気に入りに地点を保存するといった方法も細々と試してきた。しかし「自分はどこへ行き、そこで何があったか」を振り返ろうとするたび、データは各アプリに散らばっていて、アプリを開いて探し回るしかない。画面いっぱいの広告やローン勧誘をかき分けながらでは、とにかく遅い。

私が欲しかったものは、実はとてもシンプルだ。自分だけの地図。ひとつのピンがひとつの記憶で、文章が書けて、画像が貼れて、時間順に線でつながる。そしてデータは永遠に自分の手元にある。

そこで、AI と相談しながら開発したのがこのプロジェクト——Hexo ブログに埋め込む、純フロントエンドの足跡マップだ。この記事では、要件の議論から公開までの流れを、代表的な3つの落とし穴と、未来の自分への TODO とともに記録する。

一、要件のすり合わせ:元宝(Yuanbao)との2ラウンドの議論

このプロジェクトでは、いきなり AI にコードを書かせたりはしなかった。まず元宝(Yuanbao)と要件について話し、何度ものやり取りの中で AI にひたすら問いを投げてもらい、自分の要件を掘り下げていった。この過程は、自分の考えを整理することでもあった。

元宝と私は「人生の足跡を記録したい」という漠然とした思いから始め、少しずつ核となる機能に収束させていった。地図上のマーカー、分類体系(道のり/美味しいもの/美味しい飲み物/遊び/風景/興味ある地点)、Markdown の画像付きポップアップ、時間順に並べるルートモード、グループ絞り込み、データのインポート・エクスポート。

話し終えると、元宝は2つのドキュメントを産出してくれた。

ドキュメント 対象 重点
要件・機能設計ドキュメント 人間が読む 「何を作るか」「なぜ作るか」、インタラクションのルール
AI 開発者向け引き継ぎドキュメント AI 開発者 データモデル、優先度(P0/P1/P2)、受け入れチェックリスト、開発上の制約

1つ目はユーザー向けの要件記述、2つ目は AI 開発向けの実践ガイドにあたる。ただ、実際に使ってみて思ったのは、手を動かす AI を2つ目のドキュメントに完全に従わせないほうがいい、ということだ。AI ごとに思考力が違うこともある。それに、実際の作業の中で AI が人間のテストと組み合わさると、最初の技術設計と現実との不一致が必ず見つかるものだ。

最初に AI と方針を話そうと思ったのは、私自身が行政システムの仕事の中で、顧客とプロトタイプを前に延々と議論する機会が多いからだ。これが要件を掴むために非常に重要で、やらないと後工程が無限の作り直しになる。AI 開発は、AI が疲れないので何度でも作り直せる。だが要件が曖昧なら、私のトークンと時間もコストだ。最初の段階で、要件ドキュメントのレベルで方向をできるだけ正確に書いておけば、AI が正しい実装方針にたどり着く確率が最も高くなり、押し問答の回数も減る。

作業する相手と話すときは、言葉を効率的に使うべきだ。AI に考えを説明するのは、仕事の協働と伝達を鍛える良い練習になる。それに、プロジェクトが長引き、ユーザーとのやり取りが増えた挙げ句、かえって問題だらけになった経験も私にはある。AI と協働してプログラミングする場合、一番恐ろしいのはこれだと思う——せっかくほぼ完璧な作品を作り上げたのに、あとで AI にバグを直させたら、それまでの機能を壊してしまった、という展開。ははは。だから開発中は、AI に「各バージョンのバックアップに気をつけろ」と何度も言っておいた。

開発前には、AI と話し合い、似た事例も探して開発の参考にした。車輪の再発明を減らすためだ。MarkdownMap、leaflet-search、Exping の3プロジェクトのアーキテクチャの考え方を参考にした。

初期の構築方針はほぼ固まった。

  • 純フロントエンド:バックエンドなし、データベースなし。静的ホスティングだけで動く
  • データの自律:すべてのデータは JSON ファイル。いつでもエクスポートでき、決して囲い込まれない
  • 単一ファイル納品:CSS と JS をすべて1つの index.html にインライン化し、source/map/ に放り込めば使える

二、開発実施:WorkBuddy との6ラウンドの反復

ドキュメントを WorkBuddy に渡すと、あっという間に6バージョンまで反復した。その過程を表にまとめる。

バージョン 主な内容 性質
v1 引き継ぎドキュメントどおりに全機能を実装 初版
v2 外部依存をインライン化し、可用性を高める 最適化
v3 データ構造の調整 最適化
v4 「ポップアップを閉じると再度開けない」を修正 バグ修正
v5 検索ドロップダウンの隠れを修正+Enter で選択+OSM/Carto のベースマップ バグ修正+派生
v6 レイヤーの循環切り替えをドロップダウンに変更+Esri 衛星ベースマップ 派生要望

初版(v1〜v3)は意外なほど順調だった。引き継ぎドキュメントが細かく書かれていたので、AI はほぼ一発で形にし、受け入れチェックリストの大部分がそのまま通った。本当に時間を使ったのは後半の3ラウンドで、これは実際に使ってみてバグを検証・テストし、自分の用途を満たすかを評価する作業だった。

三、落とし穴の記録

落とし穴 1:ポップアップを閉じると二度と開かない(v4)

症状:地図のピンをクリックすると、ポップアップは正常に出る。右上の ✕ で閉じ、同じピンをもう一度クリックすると、ポップアップがどうしても出てこない。ページを更新すれば、また一度だけ開く。

このバグはとても紛らわしい。「一度目は開く」=開く処理は問題ない、「閉じると開かない」=閉じる操作が何かを汚した、ということだ。私はさまざまな操作で不具合の再現とログの収集を担当し、AI が原因を特定した。
AI が掘り当てたのは Leaflet のソースコードのレベルだった。Leaflet の Marker._openPopup の内部にはトグルのロジックがある。

1
2
3
4
5
6
_openPopup: function (t) {
...
this._map.hasLayer(this._popup)
? this.closePopup() // 開いている → 閉じる(toggle 動作)
: this.openPopup(t.latlng);
}

一方で、私の要件に合わせて AI が独自に書いた click ハンドラと、Leaflet 内部のこのハンドラが同時にクリックを監視していた。両者が衝突していたのだ。サードパーティのライブラリを参考にしたことで、自分から厄介ごとを招き入れた形だ。つまり、コピペエンジニアではダメだということ。ははは。

四、派生要望:使う中で改善する

この3つの機能は最初の要件ドキュメントにはなく、使っていて痛みを感じたところから出てきたものだ。

  1. 海外のベースマップ。もともと高徳地図のタイルを使っていたが、国内ならまだしも、海外の地点をマークしようと拡大すると真っ白だ。そこで OSM と Carto の淡色という海外向けベースマップを追加し、さらに Esri 衛星を加えて「国内+海外、標準+衛星」の組み合わせが揃った。
  2. レイヤーのドロップダウン。ベースマップが2種類から5種類に増えると、元の「循環切り替え」ボタンはひどく間抜けになった——Esri 衛星にしたいだけで4回連打である。ドロップダウンに変更した。
  3. 検索の Enter 確定。ドロップダウンが隠れる問題を追う中で、自分が無意識に Enter を押して検索を確定していることに気づき、AI に追加してもらった。

五、プライバシーの検討:いったん保留にした決定

公開前に一つ思い当たった。このページはログイン不要で、自分が見るには便利だが、他人にとっても同じように便利に、私の足跡のすべてが見えてしまう——どこへ行き、いつ行き、何を書いたか。

私は2つの案で迷っていた。ページにパスワードロックをかけるか、ページは普通に開けるがデータの読み込みに認証をかけるか。しかし AI と議論しても、すぐには良い方法が出なかった。静的ホスティングにバックエンドはなく、フロントエンドでのパスワード検証など目隠しにすぎない——ページを迂回して data.json の URL に直接アクセスすれば、平文データはそのままダウンロードできてしまう。

本当に有効なのは3つ目の案だ。data.json そのものを AES-256-GCM で暗号化し、パスワードは自分の頭の中だけに置く。 ブラウザ側が復号して描画し、エクスポート時は暗号化する。他人がファイルをダウンロードしても、ただの文字化けの山だ。ただ、地図の便利な共有・展示の能力は残しておきたかった。どう両立させるかは、その時点では決めきれなかった。

六、公開とデプロイ

デプロイは簡単だ。source/map/ フォルダ全体(index.htmldata.jsonREADME.md、計3ファイル)を本番リポジトリのディレクトリにコピーし、git push すれば、あとは CNB のクラウドビルドがすべて自動でやってくれる。

公開後の URL は https://misaka10013.cn/map/

プロジェクト全体の技術スタックを最後に一文でまとめると:Leaflet.js が地図を、marked.js が Markdown 描画を、localStorage が永続化を、素の JS + インライン CSS がすべてのインタラクションを担う。フレームワークなし、バックエンドなし、ビルドなし。

七、検討事項と TODO(未来の自分へ)

未完の事項をここに記しておく。後でこの記事を見返したとき、次にどこへ進めばいいか分かるように。

事項 説明 優先度
data.json の暗号化 AES-256-GCM + PBKDF2。クライアント側で復号して描画。エクスポートも同様に暗号化
地図ページへの入口 現状、ナビゲーションにも記事にも /map/ へのリンクはなく、URL 直打ちのみ(ある意味、目立たない)

あとがき

プロジェクト全体を振り返ると、私の進め方はこうだ:元宝が要件の収束とドキュメント産出を、WorkBuddy が実装を、私が実環境でのテストと方向の決定を担う。 3つの役割のうち、AI が2つを担い、方向を示し品質を評価する部分は私が握っている。


更新記録(2026-09-23):公開後の v7 → v20

上の記事は v6 の公開で締めくくっている。しかし実際に使ってみて分かったのは、公開はスタートにすぎないということだった。その後の1か月余りで、私は細々と AI と何十回も反復し、細部の問題を片付けていった。今回の更新でその続きを補う。

v7 〜 v9:実データ投入、3つの落とし穴を連続で

公開後の最初の作業は、自分の実際の足跡データを入れることだった。流れはこうだ。Google マップの Takeout で KML をエクスポート → スクリプトで Excel に変換(分類は私が目で一遍確認)→ AI にスクリプトを書かせて data.json に一括変換 → 足跡ページへインポート。276 地点、期間は 2018 年から 2019 年まで。

データが入った途端、問題が3つ同時に噴き出した。

バージョン 問題 根本原因
v7 地点名がすべて undefined、編集欄は空 取り込んだデータは地点名に name フィールドを使っていたのに、ページ側のコードは m.title を読んでいた。AI に修正させた
v7 編集を保存するとポップアップの閉じるボタンが効かない 保存時にマーカーを再生成したが、新しいポップアップにイベントを再バインドしていなかった。AI のバグ
v8 地点の時刻を変えたのに、線の連番が変わらない 日付フォーマットの問題
v9 同じ地点が高徳と Esri で数百メートルずれる 座標系の不一致

v7 のフィールドのずれ:データとページのコードがそれぞれ勝手にフィールド名を書いていたのだ。AI がこういうミスをやらかすとは思わなかった。コードは書けるが、時に初歩的なミスをする。コンテキスト記憶に起因する問題のように見える。テストデータが少ないうちは全く気づけないが、データが増えると一気にバグる。

落とし穴 4:日付フォーマットの「半分の形式」(v8)

症状:ある地点の日付を変えて保存しても、線の連番がびくともしない。JSON をエクスポートして再インポートしても、順序は元のまま。原因はこうだ。

  1. KML が出力する時刻は "2018-01-01"——日付だけで、時分がない
  2. 一方、HTML ページの <input type="datetime-local">YYYY-MM-DDTHH:MM の完全な形式しか受け付けない"2018-01-01" を渡すと、エラーも警告も出さず、黙って空欄表示になる
  3. 私は編集ポップアップを開き、時刻欄が空なので「元から入っていない」と思い込み、他の項目を直して保存を押した
  4. 保存ロジックは空の値をそのまま書き戻した——m.time = ''時刻は私の手で消された
  5. 線を引く機能には m.time && m.time !== '' という絞り込み条件があり、時刻が空の地点は線のリストから除外される。つまり「連番が変わらない」のではなく「この地点が並べ替えに参加していない」のだった

修正:すべての地点データを走査し、時刻を YYYY-MM-DD 00:00 の形式で統一して補完した。

落とし穴 5:座標系——地図プロジェクトが避けられない一段(v9)

症状:国内の地図は解像度が正直いまいちなので、OSM、Carto、Esri といった海外のベースマップを追加した。ところが切り替えてみると、以前 Google の地点で困った問題にまたぶつかった。すべての地点がずれている——国内では数百メートルほど。この問題は、私たちが作っている行政システムでもよく見かける。座標系が違うために、ピンの位置がどうしても合わないのだ。

根本原因:国内の地図サービス(高徳、百度、騰訊)は GCJ-02(通称・火星座標系。法令により非線形のオフセットが加えられている)を使い、海外のサービス(OSM、Esri、Carto)は WGS-84 を使う。同じ緯度経度の組でも、2つの座標系では同じ場所を指さない。幸い、どこが何を使ってどれだけずれるかは既知のことなので、解決の道はあった。

GCJ-02 で統一して保存する(メインで使う国内ベースマップとネイティブに一致する)。描画前に現在のベースマップを判定し、WGS-84 の国際ベースマップなら座標を WGS-84 に逆変換してから置く。逆に、ページ上で地点を追加したり位置を入力したりするときは、表示されている WGS-84 の座標を GCJ-02 に戻してから保存する。

v10 〜 v13:277KB の単一ファイルを分割する

足跡マップのコードがどんどん大きくなっているのに、AI はそれをずっと1つのファイルに入れていた。スタイルも JS もその中にある。バージョンを反復するたびに AI は全ファイルを読むことになり、すぐにコンテキストウィンドウが足りなくなって、AI は目に見えて頭が悪くなった。これはつまり、プロジェクトがもう小さくなく、複雑度が上がったということだ。ならばフレームを分割するしかない。HTML、JS、CSS をすべて分離する。

バージョン 要望 結果
v10 地点を時間帯で絞り込む(地点と軌跡を一緒に切る) 「すべて/直近1年/直近3年/カスタム」を追加。既定は直近1年
v11 AI は単一ファイルをもう編集できなくなった 277KB を index.html(4.7KB) + app.jsstyle.css に分割
v12 200KB 超の app.js をさらに分割・最適化 78% はサードパーティライブラリ。それを vendor/ に切り出し、AI が自分の書いた主要ロジックを読むたびにライブラリ JS を読まなくて済むように。業務コードは 56.6KB / 1634 行まで減った
v13 天地図の Key を取得し、よりクリアな国内衛星図源を追加 天地図の衛星ベースマップ(影像+中文注記のオーバーレイ)を追加

v12 の状況は注目に値する。プロジェクトを続けていくと規模が大きくなり、AI の処理能力を超えやすい。分割と分離は良い方法だ。

v14 〜 v20:プライバシー対策を「目隠し」から本物の暗号化へ

これが直近の反復の主軸であり、前述・第五節の「いったん保留にした決定」が最終的に着地する過程でもある。方針は3度変わった。

バージョン 方針 却下された理由
v14 ページ全体のロック:ページに入るのにパスワードが必要 しかしこの方針では、他人が data.json を直接ダウンロードして見ることは防げない。コンピュータに詳しくない人を止めるだけだ。しかもこの方針では、他人と見せ合う価値のある半公開の地点も見えなくなってしまった
v15〜v16 地点レベルの秘匿:地点に「秘匿かどうか」の属性を付け、ロック解除までは表示しない 隠すべきものだけを隠す。しかしデータは依然として平文で、「インターフェースに出さない」だけ。インターネットを理解している人なら、ページの構造を辿って data.json をダウンロードし、秘匿地点のデータをすべて取得できる
v19 本物の暗号化:公開地点は平文+秘匿地点は一括で暗号文 最終的にブレインストーミングで決めた方針

v19:暗号化の実装

最後に、この本物の暗号化という方針を実際に作り切った。核心は一文で言える。他人が data.json をダウンロードしても、ただの文字化けの山だ。

  • ハイブリッドな単一ファイル:公開地点は平文のまま(ページは普通に開き、公開地点も普通に表示される)、秘匿地点は配列ごと1つの暗号文ブロックに暗号化する——「秘匿の足跡地点がいくつあるか」すら分からない
  • アルゴリズム:PBKDF2-SHA256 を60万回反復して鍵を導出 → AES-256-GCM(12バイトのランダム iv、128ビットの認証タグ)

更新後の TODO(未来の自分へ)

事項 状態
data.json の暗号化 ✅ 完了(v19)
地図ページへの入口 ✅ 戻るボタンを追加(v17)。記事からのリンクは未対応

ひとつの感想

半年前から行政の大型ダッシュボードシステムに携わるようになったころ、自分用の地図を作りたいという思いが芽生えた。

作り始めたときは、数バージョンで地図はできた。ところが使い続けるうちに、なんと20バージョン近くまで派生して作り直すことになるとは思わなかった。まあ、これも当然ではある。1つのバージョンで自分の要件を書き尽くせることなど、決してない。使う中ではいつも要件の改善とバグ対応が付きまとう。それでも、AI の能力にはやはり驚かされる。この過程では絶えず AI の能力の限界を試していた。AI はつまずきながらも、最後にはどれも形にしてくれた。