Track #54(耕耘あと(作業機幅を含む帯)表示のPC先行開発方式の検討・実装)で作った、PC上で.dtrk記録データから耕耘あと(アンテナ―作業機オフセット・作業機幅を反映した帯)を確認するためのツール群。
タブレット実機で旋回判定閾値・帯ジオメトリの見た目を試行錯誤するのは効率が悪いため、まずPC上で記録済みの.dtrkファイルを読み込んで方式を確立し、そのロジックをタブレットのリアルタイム描画にそのまま反映する開発方針(Track #54)の一部。
判定ロジック(オフセット変換・旋回区間除外・帯ジオメトリ計算)は、PC・タブレットで翻訳・二重実装しない。Android非依存のKotlin/JVMモジュール(track-core)にロジックを1つだけ実装し、PC側CLI(pc-tool)とタブレットアプリ(:app)の両方から同一コードとして参照する。地図描画(ブラウザ側 vs Android MapLibre Native)だけがプラットフォーム固有で、判定ロジックとは独立した「表示の器」の違いとして扱う。
なお、可視化に使っているtools/map(Python + MapLibre GL JS)自体は、元々P2(Issue #2、地図表示プロトタイプ、closed)の成果物であり、耕耘あと専用に新規作成したものではない。今回はP2の資産(オフライン基地図・筆ポリゴンオーバーレイの仕組み)を流用して/api/trajectoryエンドポイントを追加する形で拡張した。この作業をP6(Issue #38)配下のTrack #54として記録しているが、内容的にはP6(タブレットアプリ)専用の作業ではなくPC側ツールに閉じた部分を含む。Issue構成の整理(P6から独立させるかどうか)は未確定のまま保留している。
.dtrkファイル
│
▼
track-core (Kotlin/JVM、Android非依存)
- DtrkFileReader/DtrkFileWriter/DtrkFormat(.dtrk読み書き、V1〜V3)
- TrajectoryOffsetTransform(アンテナ―作業機オフセット適用・旋回区間除外)
- SwathGeometryBuilder(作業機幅から帯ポリゴンを計算)
│ │
▼ (JVM CLIとして参照) ▼ (Android appから参照)
pc-tool :app (タブレットアプリ、再生画面のみ接続済み。
- GeoJSON出力 MapScreenのライブ表示は未接続、次回)
│
▼
tools/map/server.py
- /api/trajectory: pc-toolをサブプロセス実行しGeoJSONを中継
- /api/trajectory/files: 表示対象として選択可能なファイル一覧
│
▼
tools/map/static (ブラウザ、MapLibre GL JS)
- 基地図・筆ポリゴンに重ねて、耕耘あとの帯・中心線を表示
apps/android-tablet/配下は3モジュール構成になっている。
| モジュール | 種別 | 役割 |
|---|---|---|
:track-core |
Kotlin/JVM(Android非依存) | .dtrk読み書き・オフセット変換・帯ジオメトリの判定ロジック本体 |
:pc-tool |
Kotlin/JVM(applicationプラグイン) |
track-coreを使うPC向けCLI。.dtrk→GeoJSON変換 |
:app |
Android application | タブレットアプリ本体。track-coreに依存 |
apps/android-tablet$ ./gradlew :pc-tool:installDist
apps/android-tablet$ ./pc-tool/build/install/pc-tool/bin/pc-tool <dtrk-file> [<dtrk-file>...] [options]
| オプション | 内容 |
|---|---|
--out=<path> |
出力先ファイル(省略時は標準出力) |
--offset-forward-cm=<int> |
アンテナ―作業機オフセット(前後、cm)。省略時はファイルヘッダの値(V2/V3) |
--offset-lateral-cm=<int> |
同(左右、cm)。省略時はファイルヘッダの値 |
--implement-width-cm=<int> |
作業機幅(cm)。省略時はファイルヘッダの値(V3のみ)。0以下なら帯を出力しない |
--cog-trust-min-speed-mps=<double> |
COG信頼最低速度(m/s)。省略時はレイヤー0既定値 |
--turn-yaw-rate-threshold-deg-per-sec=<double> |
旋回判定ヨーレート閾値(度/秒)。省略時はレイヤー0既定値 |
--turn-speed-threshold-mps=<double> |
旋回判定速度閾値(m/s)。省略時はレイヤー0既定値 |
--up-to-epoch-ms=<long> |
時系列表示(再生)用。各ファイルの生点列をこのepoch msまでに切り詰めてから変換する |
出力はGeoJSON FeatureCollection。中心線(properties.kind == "centerline"、LineString)と帯(properties.kind == "swath"、Polygon)を、描画区間(properties.renderSegmentId)・元ファイル(properties.sourceFile)ごとに別Featureとして出力する。トップレベルのtimeRange(startAtMs/endAtMs、epoch ms)は、選択した全ファイルの生データ(トリム前)基準の時刻範囲で、--up-to-epoch-msで絞り込んでも変わらない(再生UIが全体の長さを把握するために使う)。
複数ファイルを渡した場合、各ファイルは独立に(.dtrkのsegmentIdをまたいで混ぜずに)処理される。--up-to-epoch-msによるトリムは各ファイルの実際の記録時刻(atMs、epoch ms)を共有タイムラインとして使うため、記録時刻が離れた複数ファイルを渡すと、後のファイルは実際にその時刻へ達するまで出力に現れない。
tools/map$ python3 server.py --port 8000 \
--trajectory <dtrk-file> [--trajectory <dtrk-file>...] \
replay <nmea-file>
| オプション | 内容 |
|---|---|
--trajectory <path> |
表示対象の.dtrkファイル。複数指定する場合は--trajectory a --trajectory bのように繰り返す(nargs='+'はサブコマンドのトークンまで貪欲に飲み込むため使わない) |
--pc-tool-bin <path> |
pc-tool実行ファイルへのパス(既定: apps/android-tablet/pc-tool/build/install/pc-tool/bin/pc-tool) |
--trajectory-arg <arg> |
pc-tool呼び出しへそのまま渡す追加引数。繰り返し指定可 |
replay/liveサブコマンドは既存のP2機能(/api/fixによるライブ位置表示)用で、--trajectoryとは独立に動作する。--trajectoryを指定しない場合、耕耘あと関連のエンドポイント・UIは無効のままで、既存のtools/map利用方法(NMEAログ再生・ライブ位置表示)を妨げない。
/api/trajectory(GET)--trajectoryで指定したファイルを対象に、リクエストのたびにpc-toolをサブプロセス実行し、標準出力(GeoJSON)をそのまま返す。判定ロジック自体はサーバー側に一切持たず、pc-tool(=track-core、タブレットと共有)の実行結果を中継するだけ。
ブラウザをリロード、またはブラウザ側UIでファイル選択・作業機幅を変更するたびに再実行されるため、pc-tool側のロジック(閾値・帯幅の計算式)を直してリビルドするだけで、サーバー・ブラウザ側のコード変更無しに最新の結果を確認できる。
クエリパラメータ:
file=<ファイル名>(繰り返し指定可): 表示対象を絞り込む。省略時は起動時の全ファイル。explicit=1と併用しない場合、fileが0個でも「未指定」として全ファイルを対象にする(初回読み込み時の既定動作)explicit=1: fileを明示的な選択として扱う(0個なら「何も選択しない」= 空のFeatureCollectionを返す)。ブラウザのファイル選択ドロップダウンが送る(常に1ファイルを指定するため、実際に0個になることは無い)implement_width_cm/offset_forward_cm/offset_lateral_cm/cog_trust_min_speed_mps/turn_yaw_rate_threshold_deg_per_sec/turn_speed_threshold_mps/up_to_epoch_ms: pc-toolの同名オプションへそのまま渡す/api/trajectory/files(GET)--trajectoryで指定したファイル名の一覧を返す。ブラウザ側のファイル選択ドロップダウンの生成に使う。
tools/map/static/index.htmlを開くと、右側に「耕耘あと」パネルが表示される(--trajectory未指定時は非表示)。
TrajectoryReplayerと同じプリセット)、経過/全体時間の表示。実時間500ms間隔でpc-toolを再実行して更新するため、pc-toolのJVM起動コストの分だけカクつく(滑らかな60fps的アニメーションではない)ファイル選択を単一選択にした理由: 当初は複数ファイルを同時選択できたが、--up-to-epoch-msは選択した全ファイルの実際の記録時刻(epoch ms)を共有タイムラインとして扱うため、記録時刻が離れた2ファイルを選ぶと後のファイルは実際にその時刻へ達するまで現れない。利用者から「ファイル二つ選択している時に、二つ目が再生されない」との報告があったが、調査の結果バグではなく設計通りの挙動だった(1つ目の約6分後に記録開始したファイルは、1倍速だと6分待たないと現れない)。複数ファイルの時系列合成という分かりにくい概念自体を無くす方が良いという利用者提案を採用し、常に1ファイルだけを対象にする単一選択へ変更した。
tools/mapの背景地図選択肢のうち、「地図(OpenFreeMap)」「オフライン地図(自己ホスト)」はOpenStreetMap(OSM)のクラウドソースデータが元になっている。利用者が耕耘あと表示を実データで確認中、対象地域(四万十町周辺の農村部)でこの2つの背景の道路・河川位置が、筆ポリゴン(農林水産省データ)・航空写真(地理院タイル、国土地理院の測量データ)と比べて大きくずれていることを発見した。航空写真・筆ポリゴンは相互に一致しており正確。
原因は、対象地域でのOSM編集者による道路・河川の手作業トレース精度が低いためと推定している。外部データソース自体の精度問題であり、コード側で修正できる性質のものではない。対処として、tools/map/static/map-config.jsonのdefault_backgroundを"openfreemap"から測量ベースで正確な"gsi_seamlessphoto"(航空写真)へ変更した。OpenFreeMap自体は選択肢としては残している(削除はしていない)。この変更はTrack #54固有ではなくP2(Issue #2)の既存資産に関するものだが、Track #54の検証中に発見・対処したためここに記録する。
初弾スコープ(Track #54で確認済み): 各描画区間(同一renderSegmentId)ごとに、中心線を左右implementWidthCm/2ずつ平行移動した2本の境界線を作り、それを繋いで1枚の帯ポリゴンにする単純な方式。自己交差・オーバーラップ(急旋回区間などで帯同士が重なるケース)の処理は行わない。
実装時、帯の向き(各点をどちらへオフセットするか)を中心線の前後2点の位置差分から計算していたが、実データ(samples/fieldData、2026-08-05実機フィールドテスト記録)で検証したところ大量の自己交差を引き起こす不具合と判明した。
原因は、.dtrk v2でCOG(進行方向)を追加した理由(軌跡記録バイナリ形式仕様「COG・速度の由来と符号化の根拠」参照)と同じ問題。停止に近い低速時はcm級のGPSジッターがそのまま位置差分ベースの方位の乱れに直結し、隣接点間で帯の左右が入れ替わって自己交差する。すでに記録済みで安定しているRMC由来のTrackPoint.courseDegを使わず、自前で(ノイズに弱い方法で)再計算していたのが原因だった。
修正: 区間内にcourseDegが1つでもあれば、それを優先して使う(courseDegがnullの点は直前の既知の値を保持する。P6-11のメカニズム1と同じ考え方)。courseDegが区間内に一度も現れない場合(V1形式のファイル等)のみ、位置差分+循環平均のロジックへフォールバックする。
修正後、実データ(width=150cm)で「見た目に分かる規模(1m以上)の自己交差」は2ファイル合計2件まで減少した(修正前は同条件で数千件、そのほとんどは停止時のcm級ジッターによる誤検出)。
利用者要望「タブレット側でも、ブラウザアプリと同様に、記録ファイルから軌跡を表示できるようにする(リアルタイム表示は次回)」を受け、再生画面(TrajectoryReplayScreen、記録一覧から1件選んで再生する画面)にも帯を表示できるようにした。ライブGPS表示(MapScreen)は対象外のまま(次回)。
OfflineMapStyle.ktの軌跡描画(updateTrajectoryLine)はMapScreen(ライブGPS+起動時の永続化データ読み込みが1つのStateFlowにマージされている)とTrajectoryReplayScreen(1ファイル分の点列を直接受け取る)の両方から共有で呼ばれている。MapScreen側はライブ表示と永続化データ読み込みを呼び出し側で区別できないため、帯表示を追加すると意図せずライブ表示にも影響してしまう。TrajectoryReplayScreenは独立した呼び出し口(1ファイルの点列を直接受け取る)を持つため、ここにだけ帯描画(addSwathLayer/updateSwathFill、OfflineMapStyle.ktに新設)を追加した。
帯表示を追加した直後の版は、中心線・帯を最初から全区間分描画したままにし、現在地マーカー(青丸)だけがその上を動く方式だった(P6-10からの既存挙動を踏襲)。利用者から「実機で確認したがPC版と動作が違う。表示された軌跡の上を点が移動する形態になっている。スライドバーも出ていない。PCと同じ仕様には見えない」との指摘を受け、以下の通り作り直した。
--up-to-epoch-msと同じ考え方)。再生位置までの点列(points.takeWhile { it.atMs <= 起点+経過ms })でupdateTrajectoryLine/updateSwathFillを毎ティック(100ms)呼び直す。現在地マーカーは従来通り点列の間を補間した位置に表示する。Sliderによるドラッグ可能なシークバー(任意の時刻へ直接ジャンプできる)、▶/⏸アイコンの再生/一時停止トグル、⏮の最初から戻るボタン、速度表示、経過/全体時間表示を、ダーク背景の専用コントロールバーにまとめた。テキストボタンの縦並び(「再生」「1倍速」ボタン)は廃止した。実機(Redmi Pad SE 8.7)で、軌跡・帯が再生位置に応じて伸縮すること、シークバーのドラッグで任意の位置へジャンプできる(軌跡・帯もその位置まで再構築される)ことを確認した。
ここまでの確認は幅8mなど誇張した値の合成データが中心だったため、実際のロータリー幅に近い150cmでも意図通り動くかを実データで確認した。samples/fieldDataのV2実データ(作業機幅を持たない)を、track-core(DtrkFileReader/DtrkFileWriter)を使って同じ点列・同じオフセットのままimplementWidthCm=150のV3へ変換し(変換自体は一時的なツールで実施、リポジトリには含めない)、実機のtrack/records/へ配置して記録一覧・再生画面で確認した。
全体を見渡す縮尺(往復3km超の移動)では150cmの帯は視認できないほど細く見えたが、これはバグではなく実際の値としての妥当な結果だった。ダブルタップでズームインすると、実際の走行経路に沿って帯が正しい太さ・位置で描画されていることを確認できた。帯の視認性は表示縮尺に強く依存することが実データで裏付けられた(合成データでの誇張された幅では気づきにくかった点)。
既存の再生画面は、オフセット(offsetForwardCm/offsetLateralCm)を「現在アクティブなプロファイル」から取っていた(P6-11/Track #50時点の実装)。これだと、記録後にプロファイルの値を変更すると、過去の記録が誤った設定で再生されてしまう。今回、作業機幅を合わせて追加するにあたり、PC側ツール(pc-tool、既定でファイルヘッダの値を使う)と同じ考え方に揃え、オフセット・作業機幅とも再生対象ファイル自身の.dtrk V3ヘッダに記録された値(DtrkFileReader.readOffsets)を使うよう変更した(MainActivity.kt)。V1/V2ファイル、またはヘッダが読めない場合は全て0(オフセット無し・帯なし)。
PC版に合わせて帯の色を深緑(#2f7d32、不透明度0.35)にしたところ、実機で表示してもほぼ見えなかった。FillLayer自体が描画されていないのかと思い、不透明度1.0の赤で試したところ問題なく表示されたため、レンダリングのバグではなく、色が航空写真の背景(森・畑の緑)に近すぎて実質見えなくなっていただけと判明した。地形と混同しにくい黄色系(#ffd600、不透明度0.5)に変更した。PC版と色を合わせる方針は撤回し、タブレット側は独自の色を使う。
実機(Redmi Pad SE 8.7、releaseビルド)で確認した。作業機幅を持つ合成テストデータ(V3形式、幅8m・オフセット3m)をtrack/records/へ直接配置し、再生画面で黄色い帯がオフセット位置・幅どおりに描画されることを確認した。実際のフィールドテストデータ(V2形式、幅情報なし)では帯が描画されず中心線のみになる後方互換動作も確認した。
samples/fieldData/配下に、2026-08-05の実機フィールドテストで記録した.dtrkファイル(V2形式)を置いている。動作確認・回帰確認用。
tools/mapの由来元、closed)