Issue #38「P6: トラクター耕耘アシストアプリ UI仕様策定とスパイラル開発」の仕様書。P5(Issue #21、closed)で確立した技術baseline(NTRIP/RTCM3/Bluetooth SPP/測位品質判定/診断ログ/記録再生/バックグラウンド常駐)の上に、実際に使えるUIを「仕様策定→最小実装→試用→仕様修正」の反復(スパイラル開発)で作る。
P6はIssue本文だけで判断・仕様を追うと、サイクルを重ねるほど散らばって追いにくくなる。このドキュメントを判断・仕様の集約先とし、各サイクル(Track)の実施記録・判断はIssueとTrack Ledgerへ、確定した仕様・方針はこのドキュメントへ書く。
画面全体を貫く構造として次の分割を採用する。具体的なパラメータがどのレイヤーに属するかは、各サイクルでそのパラメータを扱う時に都度判断する(事前に全パラメータを割り付けない)。
測位系画面(地図)に表示する情報は、性質によって表示の重みを変える(P6-2の実機試用フィードバックより)。
常時表示とパネル/診断画面の混在は「画面が邪魔」「監視・操作・診断が混在する意味があるか」という実機試用フィードバックを受けて導入した区分(P6-2)。
測位品質バッジ(dev.drogger.tablet.ui.PositionQualityBadge、P6-3で実装・P6-4で配置確定)。P5製品要求「RTK FIX喪失、古い測位、Bluetooth切断等を明確に表示し、不正確な誘導を継続しない」に対応する、安全に直結する情報。地図画面の左上、バージョン表示のすぐ下に常時表示する(中央だと実運用時に地図の見たい場所と被るという実機試用フィードバックを受け、P6-4で中央から移動した)。レイヤー0設定画面を呼び出す7回タップは、バージョン表示とバッジをまとめた1つのタップ領域(TopStartStatusCluster)に対して効く。
| 状態 | バッジ | 条件 |
|---|---|---|
| 固定解 | 🟢 固定解 | PositionQuality.FIX |
| 浮動解 | 🟡 浮動解 | PositionQuality.FLOAT |
| 差分測位 | 🟠 差分測位 | PositionQuality.DGNSS |
| 補正受信中... | 🔵 補正受信中... | PositionQuality.SINGLE かつ NTRIP接続済み(補正は届いているが未収束) |
| 単独測位 | 🔴 単独測位 | PositionQuality.SINGLE かつ NTRIP未接続(補正の当てがない) |
| 測位無効 | ⚫ 測位無効 | PositionQuality.INVALID(SINGLEより深刻、座標自体が信用できないため単独と別色) |
| 情報が古い | ⚪ 情報が古い | PositionQuality.STALE(質を問わず一律。positionQualityOfが以前の品質を保持しない設計に合わせた) |
| 不明 | ❔ 不明 | PositionQuality.UNKNOWN |
判定ロジックはdev.drogger.tablet.model.PositionQuality(positionQualityOf()/FixState.positionQuality())として実装済みだったが、2026-08-02時点でどの画面にも配線されていないことが判明し、今回初めてUIへつないだ。
精度指標(GST実測誤差優先、無ければHDOPへフォールバック。有効な測位を持つ状態全て): 固定解/浮動解/差分測位/単独測位(補正受信中含む)で、GSTのstdLat/stdLongから求めた実測水平誤差を"固定解 1.4cm"のように付記する(FixState.gstHorizontalErrorM、P6-8で導入。「誤差」という接頭語は冗長との利用者フィードバックを受け付けない、単位のみ表示する)。GSTが未受信・鮮度切れの間はGGAのHDOPへフォールバックする(FixState.hdop、P6-6で導入、"固定解 HDOP0.8")。
UBX-NAV-PVTのhAcc(水平精度推定値、mm)をcm換算して付記する方式だったが、P6-5(Track #43)の実機再検証で撤回した: 「CFG-VALSET送信直後の1回だけ届き、その後継続的にストリーミングされない」という現象は、実際にはNAV-PVTがBluetooth SPP経由では原則として一切届かないという、P4(Issue #30/#28、docs/decisions/p4-vrsc-dependency-deferred.md)で既に確定していた既知の制限の再発現だった。DG-PRO1RWSのBluetoothブリッジは、u-bloxチップのCFG-MSGOUT rate設定とは独立に、特定message ID(NMEA GGA/RMC、RXM-SFRBX、NAV-TIMEGPS)のみを通す固定フィルタを持つ(公式APK静的解析(Issue #28)と複数の実機実験(Issue #30)で確定済み)。NAV-PVTはこのフィルタを通らないため、CFG-VALGETでUART1のMSGOUT rateが1と読み取れても、単発pollを送っても、実際には一切出力されない。原因はアンテナ設置場所等の受信環境要因ではなく、DG-PRO1RWS本体のBluetoothブリッジ実装に起因する固定的な制限であり、設定変更や再接続では解消しない。P6-4で一時的に行った「実際には届く(1件確認)」という訂正も、この再検証(サービス完全再起動を伴う)で再現しなかったため撤回した。NmeaParser.decodeGgaは元々GGAのHDOPフィールド(フィールド8)を抽出していたが、FixRepository.ingestGgaがその値をFixStateへ渡さずに捨てていた(利用者からの「精度情報は取れているはず」という指摘で発覚)。GGAはP4で確認済みの「ブリッジを通過するmessage ID」に含まれ毎秒継続的に届くため、FixState.hdopを追加してこの値を保持し、PositionQualityBadgeの精度表示をNAV-PVT hAcc依存からHDOP依存へ切り替えた。表示はcm換算せずHDOP値をそのまま示す(HDOPは距離ではなく幾何学的な精度低下率であるため)。nmea.bin、root不要でadb pull可能)を直接解析した。結果、NMEAはGGA/RMCに限らずVTG/GLL/GSA/GSV(GP/GL/GA/GB全系統)まで広く継続的に届いており、P4の「NMEA全般が絞られている」という理解は誤りで、実際は「特定のUBXバイナリ(NAV-PVT)のみが遮断される」が正しいと訂正した(docs/decisions/p4-vrsc-dependency-deferred.md参照)。GSTは既定値0(無効)で当時は一度も出現しなかった。NmeaParser.decodeGstでstdLat/stdLongを取得、sqrt(stdLat^2+stdLong^2)で水平誤差を算出してFixState.gstHorizontalErrorMへ保持、バッジに「固定解 1.4cm」として実機表示できることを確認した。GSTはGGAと独立した受信タイミングを持つため専用の鮮度判定(gstLastReceivedAt、5秒閾値)を設け、鮮度切れの間はHDOPへフォールバックする(NAV-PVT hAccで踏んだ「鮮度チェックなしに古い値を出し続ける」バグを再発させない設計)。GST出力はRAM限定・非永続のため、受信機の電源断や再接続では無効化前提に戻る。測位系画面の4主任務(地図表示・軌跡表示・基準線指定・ナビゲート)のうち、地図表示に続いて軌跡表示を実装した(P6-2〜P6-8は測位品質バッジ・設定/診断画面分離といった周辺UIが中心で、この4主任務自体には未着手だった)。
FixRepository.ingestGga)。RMCもほぼ同時刻に同じ位置を運ぶため、両方から記録すると重複点が増えるだけで意味が薄い。TrajectoryMemoryが直近15分は間引かずフル解像度で保持し、それより古い点はアーカイブへ移す。アーカイブが2万点を超えるたびにPolylineSimplifier(Ramer-Douglas-Peucker法)で単純化する(許容誤差30cmから開始し、上限を超え続ける場合は倍々に緩めてやり直す)。数時間の連続稼働でもメモリを圧迫しない設計(利用者フィードバック「メモリには上限を設けて、一定時間経過後に古いものから最適化」)。
FileTrajectoryStoreが全点(間引きなし)をtrack/trajectory.jsonl(外部ストレージ、1行1JSON、単一ファイルへ追記)へ保存していたが、P6-10で.dtrk形式(1セグメント=1ファイル、[docs/development/p6-trajectory-file-format.md])へ置き換えた。詳細はP6-10の節を参照。アプリ・受信機の再起動をまたいで蓄積され続け、利用者が「軌跡をクリア」を押すまで消えない(利用者フィードバック「永続化する(DB/ファイル保存)」)。実機で、アプリプロセスをam force-stopで完全終了させた後も再起動時に記録が継続する(同じファイルへの追記を再開する)ことを確認した。WorkControls、画面左下)でメモリ・永続化ファイルの両方をリセットする。実機でタップ→ファイルが空になることを確認した。FixRepository.isRecording(既定値はサービス層で決定)を追加した。記録停止中はGGAの有効fixが届いても軌跡点を記録しない(測位・品質表示など軌跡以外の処理には影響しない)。記録中/停止中の状態はSharedPrefsTrajectoryRecordingStoreで永続化し、既定は停止中(初回起動時は利用者が明示的に開始するまで何も記録しない)。TrackPoint.segmentIdを導入した。「記録を再開」(直前と同じsegmentとして続きから記録、線がつながる)と「新しい記録を開始」(segmentIdを増分、線がつながらない)を区別できるようにし、地図描画(MapScreenのLineLayer、segmentIdごとに別のLineStringとして描画)・メモリ上のRDP単純化(TrajectoryMemory.simplifyPerSegment、segment境界をまたいで単純化しない)の両方をsegment境界を尊重するよう変更した。一度も記録したことがない状態では「再開」の対象が無いため「軌跡記録を開始」1つだけを表示し、既存の軌跡がある状態で停止中は「記録を再開」/「新しい記録を開始」の2つを出し分ける。FileTrajectoryStoreの永続化形式にもsegmentIdを追加し、旧形式(フォローアップ1時点、segmentId無し)の行は単一セグメント(0)として読み込む後方互換を確保した。プロセス再起動時は永続化済みの軌跡からsegmentIdの最大値を引き継ぐため(restoreTrajectory)、採番が衝突しない。実装中に、segmentが多数の短い区切り(各2点等)で構成される場合、RDPは2点未満へ縮められないためTrajectoryMemoryの間引きループが無限ループしうる設計上の穴を発見し、単純化で点数が減らなくなったら上限超過のまま打ち切るガードを追加した(件数上限の厳守よりsegment境界情報の保持を優先)。実機で、既存軌跡ありの状態での「再開」(同一segmentIdで継続)・「新しい記録を開始」(segmentId増分)・am force-stop後の再起動をまたいだsegmentId継承を確認した。MapControlsをMapDisplayControls(地図/航空写真切替・筆ポリゴン表示・現在地へ戻る、画面右上に維持)とWorkControls(軌跡記録の開始/再開/停止・軌跡をクリア、画面左下へ新設)に分割した。作業制御系ボタンの追加(AB線指定・耕耘開始/終了等)はWorkControls内で完結させる想定。LineLayerで現在地マーカーの下に描画する(MapScreen.ktのaddTrajectoryLayer/updateTrajectoryLine)。実機は静止状態での確認のため、cm〜mm級のジッターが地図上で線として視認できるほどの移動量にならず、線描画そのものの目視確認はできていない(次回、実際に移動しながらの試用で確認する必要がある)。TrajectoryMemoryの上限設計(件数ベースの間引き)はレート想定に依存しないため実害はないが、GST/HDOP等の鮮度閾値(5秒)や将来の間引き閾値を検討する際は、この実測レートを前提にすること。開始/停止ボタン追加前の検証セッションでは、実機を稼働させたままにしていたためtrajectory.jsonlが約9.7万行(実測レート約9〜10Hzのまま常時記録されていた実例)まで増えており、常時記録の実運用上の問題(圃場までの移動等が混ざる、無制限に増え続ける)を期せずして裏付ける結果になった。P6-9で実装した走行軌跡は、GGA由来のアンテナ位置をそのまま軌跡として記録・表示していた。トラクターでの実運用では、実際に耕耘しているのはアンテナの真下ではなくロータリー(作業機)中心であり、アンテナ―ロータリー間には前後・左右のオフセットがある(利用者指摘「トラクターの軌跡表示に全然手をつけていない。アンテナ位置から、軌跡として記録するロータリーの位置がオフセットされているので、それらの設定が必要」)。この要件自体はP5製品要求に「アンテナから作業機中心までの前後・左右オフセット」として既に記載されていたが、実装は一切着手されていなかった。#47(P6-9)は「移動しながらの実機試用による線描画目視確認」という別の残課題でopenのまま維持し、本節の内容はTrack #49で新規に切り出した(Track Ledgerのreferencesrelationで#47と関連付け)。
このtrackは仕様の大枠を固めるところまでを扱い、実装は次サイクルで行う。移動を伴う実機試用は手間がかかるため、利用者の意向で「ある程度実装が進んでから一度にまとめて実機確認したい」という方針とし、実装前にできる限り仕様レベルで検討を詰めた。
TrackPointへ焼き込む」方式を検討したが、利用者の実運用知見「旋回中は実際にはロータリーを上げて旋回しているだけなので、旋回中の軌跡表示自体に意味がない」を受けて方針を転換した。記録時に焼き込む方式だと、旋回区間の判定・除外ロジックを見直すたびに記録済みデータそのものを再収集する必要が生じる。このためTrackPointは従来通りアンテナ生位置のまま記録し、新たにCOG(方位)・速度もレコードへ追加した上で、オフセット変換(アンテナ位置→作業機位置)と旋回区間の判定・除外は地図描画・再生の都度計算する方式を採用した。副次的な利点として、一度COG付きで記録したデータさえあれば、実際に移動しながら画面を見て確認しなくても、屋内で再生画面(TrajectoryReplayer)にかけてオフセット計算・旋回判定ロジックを何度でも検証・調整できる。decodeRmcはCOG・速度を一切デコードしていない(P6-11で新規実装が必要)。Profile)に追加し、プロファイル切替で機体構成ごと切り替えられるようにする。COGを方位として信頼できる最低速度・旋回判定用のヨーレート/速度閾値は、機体構成に依存しないチューニング用の暫定値としてレイヤー0(アプリケーション設定)に置き、利用者が調整できるようにする。これはP5製品要求が「アンテナから作業機中心までの前後・左右オフセット」を通常設定(実測値)、「courseを方位として利用できる最低速度」を詳細設定(開発側が安全側の初期値を置き実測で確定する値)に分類していたことと一致する。.dtrkファイル形式のv2拡張: 詳細は軌跡記録バイナリ形式仕様(.dtrk)の「v2形式」節を参照。ヘッダへオフセット値(記録開始時点のプロファイル設定、監査目的)、レコードへCOG・速度を追加する。速度の符号化は当初対数圧縮による1byte化を検討したが、「今回必要なのは低速側(クリープ・旋回判定)の精度であり、高速走行時の結果で何かする仕様は考えにくい」という利用者整理を受け、対数式ではなく線形のまま単位を0.05m/s刻みにし上限12.7m/sで切り捨てる方式へ変更した(非線形の符号化・復号ロジックの実装・検証コストを避けつつ低速域の精度を確保する)。Profileへのオフセット追加(レイヤー1 UI含む)、レイヤー0への閾値設定追加、.dtrk v2読み書き、地図・再生画面での描画時オフセット変換・旋回区間除外まで実装済み(testDebugUnitTest/assembleRelease成功)。旋回判定は前後2点間の瞬時ヨーレートによる簡易実装であり、上記(1)〜(4)の未解決点はこのサイクルでも解消していない。実機確認は未実施(記録一覧のサムネイル描画は既定値のままでオフセット未反映)。詳細は軌跡記録バイナリ形式仕様(.dtrk)の「V2フォーマット」節「実装状況」を参照。FixState.lastError)FixState.numSatellites、P6-3でパネルへ追加)FixState.lastReceivedAt、「今もデータが来ているか」の生存確認。P6-3でパネルへ追加。受信センテンス数等の生カウントは診断画面のまま)パネル内のレイアウトは、現状はColumnの単純な縦積みのまま(P6-2/P6-3時点でレイアウト自体の整理は未着手)。ダッシュボード的な整理(グルーピング・アイコン化等)は次サイクルの課題として残る。
P6-10(軌跡データの保存・一覧・再生・端末外への吸い出し)の要件「記録するファイル名はレイヤー1で設定できるのが良い」を受け、レイヤー1(プロファイル設定)を最小実装した。「将来ここが拡張される前提で、かつレイヤー0と同じくAndroidのUIに準拠した方法でレイアウトする」という利用者方針に基づき、レイヤー0(Layer0SettingsScreen)と同じ作法(Scaffold+TopAppBarの「← 戻る」、SectionHeader、ListItemをタップして編集ダイアログを開く、HorizontalDivider区切り)を踏襲した。
WorkControls、画面左下)にプロファイル名そのものをボタンとして設置(当初「プロファイル: ○○」という接頭辞付きだったが、利用者フィードバックを受け接頭辞を削除)。地図表示系ボタン(画面右上)とは別領域(P6-9フォローアップの分離方針を踏襲)。Layer1SettingsScreen)でプロファイルの切替・作成を、詳細画面(ProfileEditScreen)で個別プロファイルの改名・削除を扱う。将来プロファイルへパラメータが増えても、一覧画面ではなく詳細画面側に追加していけばよいようにするための分離。
{値}_yyyyMMdd-HHmmss.dtrk)をプレビュー表示しつつ独立して編集できる。下部に赤字で「このプロファイルを削除」(「すべて初期値に戻す」と同じ見せ方)。Profile(id, name, trajectoryFileNamePrefix)。nameは一覧・切替ボタンでの表示名、trajectoryFileNamePrefixは軌跡記録ファイル名の自由テキスト部分([docs/development/p6-trajectory-file-format.md]参照)で、互いに独立して編集できる。
nameのみで表示名とファイル名自由テキストを兼ねる設計だったが、「プロファイル名しか入れられないですけど、ファイル名ルールはどうやって入れるんですか」という指摘を受け、trajectoryFileNamePrefixを独立したフィールドへ分離した。プロファイル作成時はtrajectoryFileNamePrefixをnameと同じ値で初期化する(利用者が明示的に変えるまでは一致させておく方が分かりやすいため)。旧形式(このフィールド追加前に作成されたプロファイル)は読み込み時にnameを既定値として補う後方互換を持つ。SharedPrefsProfileStoreがSharedPreferencesにJSON配列で永続化する。ProfileStore.ensureActiveProfile())。am force-stop後の再起動をまたいだ選択状態の保持を確認した。プロファイルのtrajectoryFileNamePrefixを使い、実際にセグメントを.dtrk形式([docs/development/p6-trajectory-file-format.md])で保存する部分を実装した。P6-9のJSON Lines形式(track/trajectory.jsonl、単一ファイルへ全セグメントを追記)を置き換える。
track/records/{trajectoryFileNamePrefix(サニタイズ済み)}_{yyyyMMdd-HHmmss}.dtrk。「新しい記録を開始」の瞬間に新規ファイルを作り、以降そのセグメントの間ずっと逐次追記する(保存操作なし、P6-9の方針を踏襲)。「記録を再開」は同じファイルへの追記を続ける。DtrkFileWriter/DtrkFileReader: 仕様書通りの固定長バイナリ(ヘッダ36byte+レコード10byte)を実装。1点ごとにflush()し、クラッシュ耐性を保つ。範囲超過(±93km超・elapsedMs溢れ)はクランプする(無音の暴走を避ける)。SwitchableTrajectoryRecorder: FixRepositoryはコンストラクタで固定の1TrajectoryRecorderを受け取る設計のため、セグメントが変わるたびに書き込み先ファイルを差し替えられるよう、委譲先を実行時に切り替え可能なラッパーを新設した。SharedPrefsTrajectoryRecordingStoreに「記録中かどうか」に加えて「現在書き込み中のファイルパス」を永続化するよう拡張した。記録中にプロセスが再起動した場合、DtrkFileWriter.resume()で同じファイルのヘッダを読み直し、同じセッション開始点を基準に追記を再開する。再開対象のファイルが無い・壊れている場合は、データを失わないよう新しいファイルへ切り替える(この場合のみsegmentIdも新しくなる)。records/配下の全.dtrkファイルを読み結合し、これまで通り地図に全セグメントの軌跡を表示する。各ファイルはヘッダに1つのsegmentIdしか持たないため、ファイルをまたいでも線はつながらない(既存のsegment対応描画をそのまま活用)。records/配下の全.dtrkファイルを削除するよう変更した。クリア時に記録中だった場合は、データを失わないよう新しいファイル・新しいsegmentとして記録を続ける。segmentId・実測値が妥当なバイナリであることをバイトダンプで確認)・「新しい記録を開始」による別ファイルへの切替・am force-stop後の同一ファイルへの追記再開(ヘッダのsegmentId継承を確認)・「軌跡をクリア」による全ファイル削除と記録継続、を確認した。利用者フィードバック「今、それをUI上で確認する手段がない」(記録した軌跡が実際に保存できているかUI上で確認できない)を受け、記録一覧・サムネイル・再生をまとめて実装した。
WorkControls(作業制御系ボタン領域、画面左下)に「記録一覧」ボタンを追加。TrajectoryThumbnail): 利用者要望「軌跡がある地図上の場所をサムネイル表示にするのが良い」を受け、ファイル名の列挙ではなく実際のオフライン基地図(航空写真・筆ポリゴン込み)を背景にした小さな地図として実装した。一覧の行ごとに独立した小型MapView(操作不可・軌跡の範囲に自動でカメラを合わせる)を持つ。LazyColumnは画面内の行だけを保持するため、同時に生きるインスタンス数は表示行数程度に収まる設計。
newLatLngBounds)が算出するズームが極端に高くなり、オフライン基地図(PMTiles)の提供範囲を超えてベクトルタイルが空白になる不具合を実機で発見した。フィット後のズームが上限(17)を超えたら固定ズームへ再設定するクランプをfitCameraToPoints(サムネイル・再生画面で共通)に追加して解消した。TrajectoryListScreen): 各行にサムネイル+開始〜終了時刻(記録中のものは開始時刻のみ+「記録中...」ラベル)+点数を表示。タップで再生画面を開く。TrajectoryReplayScreen)・再生ロジック(TrajectoryReplayer): 利用者要望「ファイルを選択したら、軌跡を再現する事をしたい。再現速度は可変にしたい」を受け実装した。記録時の実際の点間隔を速度倍率で圧縮/伸長して再現する(録画済みRTCMを実際の受信間隔で再生するRecordedRtcmSourceと同じ考え方)。速度は1/2/5/10/30倍のプリセットをボタンでタップして巡回させる方式(スライダーではなく、既存のボタン中心のUIと統一)。前後の記録点を線形補間して滑らかに動かす。再生ロジック自体はUIに依存しないTrajectoryReplayerとして切り出しテスト可能にした。MapScreenと同じ描画方式(OfflineMapStyle.kt)を再利用し、記録操作(WorkControls)は持たない読み取り専用の画面にした。RtkForegroundService.listTrajectoryRecords()がcurrentFilePath(記録を停止した後も「記録を再開」で同じファイルへ戻れるようあえて残している値)の有無だけで「記録中」を判定しており、isRecording(実際に記録が動いているか)を見ていないバグだったと判明した。停止済みのファイルまで「記録中...」と表示され続けていた。isRecordingがtrueの場合のみcurrentFilePathを有効として渡すよう修正した。P6-10の最後の要件「記録を吸い出して消去する部分」を実装した。方式はユーザーとの discussion により以下で確定した。
転送方式: Android標準のフォルダアクセス(Storage Access Framework、ACTION_OPEN_DOCUMENT_TREE)で、利用者がGoogle Drive・OneDriveなど「フォルダを選べるアプリ」のフォルダを指定する方式にした。USBは対象外(ユーザー判断: 「USBってのは、あんまり考えてないです」)。アプリ側にOAuth・クラウドAPIとの連携は一切不要で、DocumentFile/ContentResolverだけで完結する。
TrajectoryExportDestination抽象: 「うちでしか使わないなら自宅サーバー(keinasystem、main.keinafarm.net)組み込みが良いが、汎用性も欲しい」というユーザーの整理を受け、書き出し先をTrajectoryExportDestinationインターフェース(isConfigured()/export(file))として切り出した。今回はSAF実装(SafTrajectoryExportDestination)のみを実装し、keinasystem向けのHTTP実装等は将来この抽象の別実装として追加できるようにしてある。
.dtrkのまま書き出し、GPX変換はしない: GPXへ変換すると1点あたり約96byte(.dtrkは10byte)と約9〜10倍に膨らむ計算になり、回線速度とGoogle Driveの空き容量(ユーザー: 「Google Driveってそんなに容量大きくない」)を踏まえ、端末→クラウドは.dtrkのまま送る方針にした。GPX変換が必要になったとき(QGIS/Google Earthでの閲覧等)はPC側の別ツール(tools/*.pyの既存の作法に合わせる想定)で後から行う、将来のタスクとして切り出した。
records/exported/サブフォルダによる状態管理: 「書き出す」操作は選択された(記録中のファイルを除く)未書き出し.dtrkをTrajectoryExportDestination.export()でコピーし、成功したものだけrecords/exported/へ移動する(失敗したものは直下に残し、再度選んで書き出せば再試行できる)。一覧・サムネイル・再生はexported/配下も引き続き対象に含めて表示する(過去の全セグメントを表示し続ける既存方針を維持)。「削除」操作は選択されたファイルを、書き出し済みかどうかに関わらず端末から削除する。書き出し先のデータ自体は消えない。
「軌跡をクリア」との関係: 「軌跡をクリア」はrecords/直下のみを削除し、exported/配下(既に書き出し先へバックアップ済み)には触れない設計にした。バックアップ済みデータを消す操作は記録一覧の「削除」に一本化し、「クリア」が誤って書き出し済みデータまで消してしまわないようにするため。
選択式の書き出し・削除への変更(フォローアップ): 当初は「未書き出し全件を一括で書き出す」「書き出し済み全件を一括で削除する」方式だったが、利用者フィードバック「一括の書き出し/削除は書き込み手順が不便」を受け、記録一覧の各行にチェックボックスを追加し、選択した記録だけを対象に書き出す・削除する方式へ変更した。記録中の行はチェックボックスを表示せず選択不可にしている。RtkForegroundServiceのAPIもexportPendingTrajectoryRecords()/deleteExportedTrajectoryRecords()(引数なし・全件対象)からexportTrajectoryRecords(filePaths: Set<String>)/deleteTrajectoryRecords(filePaths: Set<String>)(選択されたファイルパス集合を受け取る)へ置き換えた。
書き出し先フォルダ選択の確認ダイアログ(フォローアップ): レイヤー0設定の「軌跡の書き出し先フォルダ」行は、当初タップすると即座にSAFピッカー(別アプリ)が開く設計だったが、利用者から「元の画面に戻る方法がわからない」という指摘を受けた。SAFピッカーは別アプリのActivityであり、深い階層まで移動していると端末の「戻る」操作だけでは階層の数だけ戻る必要があり迷いやすい。「別アプリのせいで片付けるのは無責任」という利用者の指摘を受け、タップ直後にこの画面内で完結する確認ダイアログ(「選び直す」/「キャンセル」)を挟み、そこに「フォルダを選ばずに戻りたいときは、戻るを階層の数だけ押すか、最近使ったアプリからDroggerに切り替えれば設定は変わらない」という案内を先に載せる設計にした。これにより、確認だけしたい・誤ってタップしただけの場合はこの画面内の「キャンセル」一回で完結する。
既知の制限: Google DriveアプリはSAF経由の新規フォルダ作成に対応していない: 実機検証で、Drogger(や他のアプリ)のフォルダ選択画面からGoogle Drive内に「新規フォルダを作成」しようとすると、文字入力や日本語IME変換とは無関係にFileNotFoundException: not supportedで毎回失敗することを確認した(DriveアプリのDocumentsProvider.createDocument実装側の制限)。回避策は、Google Driveアプリ本体(またはdrive.google.com)側であらかじめ書き出し用フォルダを作成しておき、Droggerのフォルダ選択画面ではその既存フォルダを選ぶ(「開く」だけなら問題なく動作する)。
実機テスト時の注意(教訓): 実機でのSAFピッカー操作中、フォルダ階層を辿っている途中の誤タップにより、テスト用の.dtrkファイルが利用者の実際のGoogle Driveフォルダ(実務で使っている「Develop」フォルダ)へ書き出されてしまう事故が本セッション中に発生した。原因は、SAFピッカーの「キャンセル」操作の過程で実在フォルダを誤って確定してしまったこと。実機のGoogle Drive等、実データを持つ外部アカウントに接続した状態でのUI自動操作テストは、誤操作が実データに影響しうることを踏まえ、慎重にスクリーンショットで選択内容を確認しながら進める必要がある(該当ファイルはDrive側で削除・復旧済み)。
UI: レイヤー0設定画面に「軌跡の書き出し先フォルダ」項目を追加(未設定/フォルダ名表示、確認ダイアログ経由でのSAFピッカー起動、設定解除)。記録一覧画面の上部バーに選択状態に応じて有効/無効が切り替わる「書き出す」「削除」ボタンを追加し、各行にチェックボックスと、書き出し済みを示す「書き出し済み」バッジを追加した。
実機で、SAFピッカーでのフォルダ選択(アプリ名「Drogger Tablet」が正しく表示されることを含む)・恒久権限付与・レイヤー0設定への反映・記録一覧での選択式書き出し(選択した1件のみが書き出されexported/へ移動しバッジが反映されること)・選択式削除(確認ダイアログ、削除後も書き出し先のファイルは残ることの確認)・アプリ完全再起動後もフォルダ設定が保持されることを確認した。testDebugUnitTest全件成功、assembleRelease成功。
NAV-PVTはDG-PRO1RWSのBluetoothブリッジの固定フィルタにより恒久的に届かない(P6-5で確定、再調査の余地なし)。精度指標はGST(実測誤差)優先・HDOPフォールバックへ切り替え済み(P6-6/P6-8)。
GST出力有効化はRAM限定・非永続だが、RtkForegroundServiceが接続確立(BluetoothLinkState.Connected)のたびに自動送信するため、利用者の手動操作は不要(P6-8フォローアップ)。診断画面の手動ボタンは動作確認・任意タイミングでの再送信用に残している。
軌跡表示(P6-9)は実装済みだが、実機での動作確認は静止状態に限られ、実際に移動しながらの線描画の目視確認はできていない。次回の実機試用で確認する。
走行軌跡へのアンテナ―作業機(ロータリー)オフセット反映は、P6-11で仕様策定・P6-12(Track #50)で実装済み(旋回判定の閾値調整・実機確認は未実施)。「アンテナ―作業機オフセット」節を参照。
耕耘あと(作業機幅を反映した帯)表示は、Track #54でPC側ツール(pc-tool、PC側 耕耘あと表示検証ツール参照)を先行開発した上で、タブレット側にも再生画面(記録一覧から選んだ1ファイルの再生)へ実装・実機確認済み。実際のフィールドデータ(V2)をV3化し現実的な作業機幅(150cm)でも実機確認し、帯の視認性は表示縮尺に強く依存する(全体表示では見えないほど細いが、ズームすると正しい太さ・位置で描画される)ことを確認した。作業機幅はProfile.implementWidthCm(レイヤー1)として持ち、.dtrk v3ヘッダへ記録する(軌跡記録バイナリ形式仕様参照)。測位系画面(MapScreen)のライブ表示への反映は未着手のまま次回。自己交差・切り返し箇所の重なり表示は既知の残課題としてpc-trajectory-viewer.mdに記載。
WireGuardトンネルの起動忘れによるNTRIP接続失敗が実機フィールドテスト(2026-08-05)で発生し、疎通確認・警告表示をIssue #51として起票した(未着手)。
軌跡記録(.dtrk)を開始しないまま走行してしまう操作ミスが同日発生し、事実ベースで原因を追跡できるよう、利用者操作の診断ログ記録をTrack #52・#53で実装した(下記Track実施記録参照)。
測位系画面の残る主任務(基準線指定・ナビゲート/誘導)は未着手。基準線指定は状態管理(保存・呼び出し)やレイヤー1(プロファイル設定)との関係整理が必要になる可能性がある。
開閉パネルのダッシュボードレイアウト(グルーピング・アイコン化等)は未着手。ユーザー判断により、機能が増えて整理対象が増えてから着手する方針(P6-9)。
軌跡データの保存・一覧・再生・端末外への吸い出しはP6-10で完了した(Track #48)。.dtrk形式での保存(1セグメント=1ファイル)、記録一覧・サムネイル(基地図背景)・再生(速度可変)、SAF(Drive/OneDrive等)への書き出しと書き出し済み記録の消去まで実装済み。keinasystem(自宅サーバー)向けの書き出し先実装、PC側のGPX変換ツールは将来の別タスクとして残っている。
レイヤー2の中身は未着手。レイヤー1は最小実装(プロファイル名・ファイル名ルール)が完了(P6-10)。
DEFAULT_STALE_THRESHOLD基準の鮮度チェックを追加)。本trackで行った「NAV-PVTは実際には届く(1件確認)」という訂正は、P6-5での再検証により撤回された。NmeaParser.decodeGgaが抽出していたHDOPをFixRepositoryが捨てていたことが判明し、FixState.hdopを新設して配線した。PositionQualityBadgeはFIX/FLOAT限定のcm表示から、有効な測位を持つ全状態(FIX/FLOAT/DGNSS/SINGLE)でのHDOP直接表示("HDOP0.8"形式)へ変更した。実機で表示を確認済み。nmea.bin)を直接解析した。NMEAはGGA/RMCに限らずVTG/GLL/GSA/GSVまで広く届いており、P4の「NMEA全般が絞られている」という理解は誤りで「特定のUBXバイナリ(NAV-PVT)のみが遮断される」が正しいと訂正した。GSTは既定値0で当時は未出現。次の実装範囲(PDOP/VDOP追加のみか、GST有効化まで踏み込むか)はユーザー判断を仰ぎ、P6-8でGST有効化を選択した。TrackPointモデル、trackパッケージ(TrajectoryRecorder/FileTrajectoryStore/TrajectoryMemory)を新設し、FixRepositoryにGGA由来の軌跡記録・クリア・復元を追加、RtkForegroundServiceが起動時に永続化ファイルから復元するよう配線、MapScreenにLineLayerでの描画と「軌跡をクリア」ボタンを追加した。実機で、軌跡ファイルへの継続的な追記・クリア操作によるファイルの空化・am force-stop後の再起動をまたいだ記録継続(クラッシュなし)を確認した。実機が静止状態だったため線描画そのものの目視確認はできておらず、次回の移動を伴う実機試用で確認する。副次的な発見として、実機のGGA受信レートがP6-7の想定(約1Hz)と異なり実測約9〜10Hzだったことが判明した。フォローアップ: 当初のアーカイブ間引きはindexベースの単純な間引き(1つ飛ばし)だったが、「トラクターの走行はほぼ直進とUターンなので、この間引きだと古い軌跡がおかしくなる」という利用者指摘を受け、Ramer-Douglas-Peucker法による形状維持型の単純化へ置き換えた(詳細は上の「走行軌跡(P6-9)」節)。testDebugUnitTest(299件)全件成功、assembleRelease成功。NmeaParser.decodeGstでstdLat/stdLongを取得し水平誤差(sqrt(stdLat2+stdLong2))を算出、FixState.gstHorizontalErrorMへ保持、PositionQualityBadgeをGST優先・HDOPフォールバック表示へ変更した。GST専用の鮮度判定(5秒閾値)を設け、NAV-PVT hAccで踏んだ鮮度バグを再発させない設計にした。実機で「固定解 1.4cm」の表示を確認し、20秒以上経過後も鮮度ゲートを通過して表示が継続することを確認した(=継続受信の証拠、NAV-PVTの1回きり受信とは異なる)。ユーザーフィードバックを受け、cm表示から「誤差」という接頭語を削除した。さらに「手動操作なしでアプリ側から設定できないか」という要望を受け、RtkForegroundServiceがBluetoothLinkState.Connected遷移のたびにsendGstUart1EnableDiagnostic()を自動送信するようにした(BluetoothNmeaController本体とその既存テストスイートは変更せず、Service層の既存の状態監視処理を拡張する形で実装)。実機でアプリ完全再起動→診断画面を開かずに自動でGSTが有効化され「固定解 1.4cm」が表示されることを確認した。referencesrelationで関連付け)。オフセット適用は記録時ではなく描画・再生時に行う方式へ転換し(旋回中は作業していないため軌跡表示に意味がないという利用者の実運用知見による)、TrackPointはアンテナ生位置のまま維持しつつCOG・速度を新たに記録する。COG無効時(低速・停止)は直前の方位を保持、旋回時(ヨーレート・速度による自動検出)はその区間の線を描画しない方針とした。.dtrk形式をv2へ拡張(ヘッダにオフセット、レコードにCOG・速度を追加)。詳細は「アンテナ―作業機オフセット」節および軌跡記録バイナリ形式仕様(.dtrk)を参照。旋回判定の具体的閾値・旋回区間の表示方法・実機でのRMC表記精度確認は、実装・試走データ取得後に確定する残課題として残っている。NmeaParser.decodeRmcにCOG・速度のデコードを追加し、FixRepositoryがRMC受信のたびに保持してGGA由来のTrackPoint生成時にノット→m/s変換の上で付与するようにした。DtrkFormat/DtrkFileWriter/DtrkFileReaderをV1・V2両対応に拡張し、ドキュメントレビューで発覚した「V1のoffset6-7(reserved、実機記録済みファイルは常に0)とV2のheaderLengthは同じ位置だが意味が異なり、V1ファイルではこの値を長さとして信用してはならない」という制約を、V1は常にヘッダ長36をハードコード・V2のみheaderLengthを実読みする非対称設計として反映した。ProfileにoffsetForwardCm/offsetLateralCmを追加してレイヤー1のプロファイル編集画面に設定UIを追加し、レイヤー0にCOG信頼最低速度・旋回判定閾値(暫定値)の設定を追加した。新規TrajectoryOffsetTransform(track package、純粋関数)がメカニズム1(方位の穴埋め)・メカニズム2(旋回区間の除外)を実装し、地図画面・再生画面へ統合した。テスト作成中、旋回判定(メカニズム2)のヨーレート計算にメカニズム1で穴埋め済みの「有効方位」を使うと、低速で方位が信頼できないと判定されている間は方位が凍結して見え、実際のUターン開始(減速とともに方位が変わり始める瞬間)を検出し損ねる設計バグを発見し、RMCの生COG値を直接使う別系統のヨーレート計算に分離して修正した。testDebugUnitTest(353件)・assembleReleaseとも成功。実機での動作確認・閾値調整はTrack #49の決定通り未実施のまま次サイクルへ残した。RtkForegroundServiceの軌跡記録の開始・再開・停止・クリア操作をDiagnosticRecorderへ記録するようにした。実機で3操作すべてがevents.jsonlへ記録されること、「軌跡をクリア」が書き出し済み(exported/)ファイルに影響しないことを確認した。BluetoothGpsViewModelの全公開関数を洗い出して提示し、ログ量への懸念について利用者と設計を詰めてから実装した。DiagnosticRecorderへEventLevel(DEBUG/INFO)とminEventLevel(実行中変更可能なvarプロパティ)を追加し、FileDiagnosticRecorderがフィルタする。BluetoothGpsViewModel.logUserAction(category, message, level)を公開ヘルパーとして新設し、プロファイルCRUD・NTRIP設定・エクスポート/削除・接続/切断・NTRIP再接続・診断ボタン等の全操作、およびMapScreen/MainActivityの画面のみの操作(背景/航空写真/筆ポリゴン切替、主要画面遷移)から呼ぶようにした。データ・設定・接続に影響する操作はINFO(既定で常時記録)、画面表示のみの操作と診断専用ボタンはDEBUG(既定オフ)という基準で振り分けた。レイヤー0設定に「詳細ログ(DEBUG)を記録する」スイッチを追加し、SharedPrefsDiagnosticLoggingSettingsStoreで永続化しつつ実行中のDiagnosticRecorderへ即座に反映(アプリ再起動不要)されるようにした。実機で、既定OFF時にDEBUGイベントが記録されないこと、スイッチをONにした直後(再起動なし)に同じ操作がDEBUGとして記録されることを確認した。testDebugUnitTest・assembleReleaseとも成功。