状態: 2026-08-19にTrack #89のrestart契約で置換済み。
現行契約はnavigation-field-geometry-pipeline-contract-pattern-1.mdを参照する。本書はTrack #72の履歴baseline。
本書は履歴確認専用であり、新規実装・レビューの規範として使用しない。
Issue #69 / Track #72(境界B-2: polygon対応計画・実装契約)。境界B-1(Track #71、baseline完了)が提供する
FieldPlanningGeometry(筆・入口・方向の幾何基盤)を前提に、Issue #69コメント#4708/#4719/#4721、およびTrack #72のレビュー記録(#4718、#4724、#4726)を反映して、境界B-2以降の実装契約として書く。改訂履歴:
- 第1版(#4708のみ反映)は#4718で「五角形以上を頂点数だけで正式対応にできない」「複数区間非対応は不完全」等の指摘を受け未承認。
- 第2版(#4719/#4721反映)は#4724で「WorkAreaの粒度・coverage基準・transfer安全経路・lap/circulation・巻き方向不変性・ID責務・遅延追い詰め最適化根拠」の7点の指摘を受け未承認。
- 第3版(#4724反映)は#4726で「(1)軸定義の取り違え、(2)投影重複だけでWorkAreaを結合、(3)WorkArea内走行順の未定義、(4)coverage対象領域が安全余白を無視、(5)transfer生成とWorkArea内経路が未接続、(6)遅延追い詰めdetourの占有幅未検証、(7)K上限の適用順が逆、(8)D_max超過fallbackがハード制約を破りうる」という8点の指摘を受け未承認。
- 本改訂(第4版)は#4726の8点すべてに対応する。
- 第4版レビュー後の確定追補として、WorkArea path cover・全WorkAreaの全体同時評価・CornerTill停止点幾何・DEADHEAD移動時外幅を本文へ直接反映した。
- Track #88(境界B-2f restart再修正、Issue #69指摘)により1.5節(5)・3.1節を修正した。「開始側ごとに内部距離最小の1件だけへ潰す」設計は終端lane(=どちらのlateralAxis端で終わるか)を考慮しないため、
finishingLap3StartAnchor等(1.6/1.9節)への接続に必要な「安全だが最短ではない」経路を失いうる具体的反例が確認された(該当Track記録参照)。開始側×終端側(いずれもlateralAxisの両端に限る)の組合せごとに独立探索する設計へ改め、internalPlanCandidatesの上限を「最大2件」から「最大開始側数×終端側数(通常4件)」へ修正した。本書は文書のみであり、コードは変更していない。利用者レビューで承認された後、本書の「16. 実装境界と停止点」に従って個別Trackへ分割して実装する。承認前に実装へ着手しない。
#4708(実用性懸念)→#4719(追い詰め対象=凸頂点)→#4721(複数区間の実作業規則)→Track #72の3回のレビュー(#4718、#4724、#4726)。本書はこれらすべてを前提として組み込む。
全章を通して次の命名で統一する(前版の「headingDegと垂直な軸=各列自身の伸びる方向」という記述は誤りであり撤回する)。
| 名称 | 意味 | 用途 |
|---|---|---|
travelAxis |
headingDeg方向の軸。各中央列(pass)が実際に走行する方向 |
ある列の両端点間の距離・重なりを比較する(1章のWorkArea内接続判定) |
lateralAxis |
headingDeg + 90方向の軸。列が並ぶ横断方向(offset方向) |
列の並び順・開始側/仕上げ周回側の位置比較 |
longitudinalProjection(point) |
pointをtravelAxisへ投影したスカラー値 |
列の長手方向の位置比較 |
lateralOffset(point) |
pointをlateralAxisへ投影したスカラー値 |
列の並び順・開始側/終端側の位置比較 |
用途の原則: 隣接する列の走行区間が長手方向に重なるかを判定する場合はlongitudinalProjection(travelAxis)を使う。列の並び順・offset・中央耕開始側/仕上げ周回側を判定する場合はlateralOffset(lateralAxis)を使う。以降の章はこの命名を用いる。
headingDegの決定契約(境界B-1修正、Track #86、Issue #69コメント#4874/#4875)本書のすべての章はheadingDegを外部から与えられた入力として扱う。headingDegをどう決めるかはFieldPlanningGeometry.centerTillageDirectionCandidates(field)(境界B-1、FieldPlanningGeometryの公開関数)が提供する。
FieldDirectionCandidate)を生成する。頂点数Nの有効polygonは原則N候補で、平行な辺があっても角度で重複除去しない。headingDegはcorners[edgeIndex] → corners[(edgeIndex+1) % n]の方位角(0=北・時計回り)であり、本書のtravelAxis/lateralAxisの定義と厳密に一致する。頂点の格納順(CW/CCW)を正規化しないため、classifyVertexTurns(5章)とは異なり格納順に依存する値になる(これは仕様であり、辺identityとheadingを1:1で保つための意図的な設計)。regionId(=field.polygonUuid、現状は単一Polygon・穴なしのみ対応)・edgeIndex・edgeStart/edgeEnd・決定的なcandidateId({polygonUuid}#edge:{edgeIndex})を持つ。unsupportedShapeWarning、空candidates)。**計画全体の目的関数(利用者確定)**は次の優先順位とする。
remainingUntilledAreaを許容される数値誤差内で0にする。これを満たせない計画は原則採用しない。DOUBLE_TILLと意図的重複の面積・距離を最小化する。妥協可能なソフト条件。2と3を改善するために1を犠牲にしてはならない。安全に実行可能な完全被覆解が複数ある場合の比較は、この辞書式優先順位を全solver/composerで共通利用する。
physicalFieldGeometry)・耕耘対象(allowedTillageTarget)・中心線配置可能領域(safeCenterlineRegion)の3種へ分離する(2章、#4726指摘(4))。Kは安全候補を絞り込む上限ではなく、安全性検証を通過した候補の中で最適化計算量を抑えるための上限とする(7章、#4726指摘(7))。docs/specifications/p6-ui-spec.md(345〜348行)の「四隅」という矩形前提の記述は、当時時点で正確な正本仕様であり矛盾ではない。本書の5章が実装される段階で、境界B-2j(全体composer、16章)の実装Trackにて正本仕様を更新する。本書では独断で書き換えない。
前版までの対応表を維持する。加えて、本改訂で新たに参照する既存プリミティブを追加する(変更なし、3章で使用)。
| 用語 | 対応する既存コード |
|---|---|
| ring境界上の部分経路抽出 | JTS org.locationtech.jts.linearref.LengthIndexedLine(既存ライブラリ、JTS 1.19.0に同梱、3章で新規利用) |
| 既存のcirculation一貫性検証 | TillagePlanInvariants.validateCirculationConsistency(既存、境界A。追い詰めと仕上げ周回のcirculationが逆向きであることを要求している既存契約、4章で参照) |
CenterTillageWorkAreaは「外周移動(PerimeterDeadheadTransfer)を挟まずに連続して耕せる中央pass群の連結成分」を表す。1本の候補列や1つの交差区間の単位ではない。突出部の奥側に5列ある場合、原則として5列を含む1個のWorkAreaになる。
enum class TravelDirection { FORWARD, REVERSE }
/** WorkArea内の1passの走行計画。 */
data class WorkAreaPassPlan(
val candidateLaneId: String,
val passId: Int,
val travelDirection: TravelDirection, // FORWARD=candidateLaneの正方向、REVERSE=逆方向
val startPoint: LatLon,
val endPoint: LatLon,
)
/** WorkArea内部の走行順序・列間接続。 */
data class WorkAreaInternalPlan(
val passPlans: List<WorkAreaPassPlan>, // このリストの並び順=訪問順
val interPassConnections: List<List<LatLon>>, // passPlans[i]とpassPlans[i+1]を結ぶDEADHEAD geometry(n-1本)
val entryPoint: LatLon, // = passPlans.first().startPoint
val exitPoint: LatLon, // = passPlans.last().endPoint
val totalInternalDistanceM: Double,
)
/** 全WorkAreaの訪問順・内部経路・外周transferを一体で確定した中央耕全体計画。 */
data class CenterTillageTraversalPlan(
val orderedAreaIds: List<String>,
val selectedInternalPlans: Map<String, WorkAreaInternalPlan>,
val transfers: List<PerimeterConnectionCandidate>,
val totalDeadheadDistanceM: Double,
)
data class CenterTillageWorkArea(
val areaId: String,
val areaGeometry: List<LatLon>,
val candidateLaneIds: List<String>,
val laneSegments: List<RouteSegment>,
val internalPlanCandidates: List<WorkAreaInternalPlan>, // 1.5節、entry点候補ごとに複数保持
val selectedInternalPlan: WorkAreaInternalPlan?, // 1.6節でtransferと同時に確定した後に設定
val coverageResult: WorkAreaCoverageResult,
val remainingUntilledAreaM2: Double,
val warnings: List<NavGeometryWarning>,
)
passVisitOrderWithinArea: List<Int>(前版)という曖昧な表現を、WorkAreaInternalPlanという具体的な型へ置き換えた。
Stage 9は、候補列ごと(candidateLaneId)に0本以上のRouteSegmentを生成する。これを次の手順でWorkAreaへグルーピングする。
lateralAxis上で隣り合う候補列由来のRouteSegmentペアについて、両者をtravelAxisへ投影した区間(longitudinalProjectionの範囲)が許容誤差を超えて重なる場合のみ、次段の接続候補生成対象とする。この投影重複判定は、計算量を抑えるための事前フィルタとしてのみ使う。これ単独でWorkAreaの結合を決定しない(前版の誤りを訂正)。actionTypeはDEADHEADである。safeCenterlineRegionにcoversされる。physicalFieldGeometryの外、2章)を横断しない。RouteSegmentをちょうど1回ずつ訪れる単一路があれば1つのWorkAreaとする。単一路がなければ、全segmentを覆う安全なpath coverへ分割し、各pathを別WorkAreaとする。目的関数は、(a)全segmentをちょうど1回含む、(b)全接続が安全、(c)WorkArea数最小、(d)同数なら内部DEADHEAD距離最小、(e)決定的tie-break、の順とする。areaGeometry: 確定した各pathに属する全RouteSegmentをCAP_FLAT bufferした領域の合併をCenterTillageRegionと交差させたもの。DEADHEAD横幅の占有領域チェックについて: 3の安全性検証は接続の中心線を対象とする。移動時外幅deadheadClearanceWidthCmのbufferまで含めた検証は、値が設定された場合に最終的なTillagePlanGeometryInvariants(13章)で全DEADHEAD接続へ適用する。未設定時は中心線保証に限定する(9.2節)。
lateralAxis上で隣り合う候補列A・B(RouteSegment)について、Aの走行方向をtravelAxis上で正方向(FORWARD)とした場合、Aの終点(travelAxisの値が大きい側)からBの対応する端点(同じくtravelAxisの値が大きい側)への直線を接続候補とする(通常のboustrophedon往復と同じく、隣接列は端で折り返して逆方向(REVERSE)に走行する)。AとBの長さが大きく異なる場合(polygon形状により列長が変わる、前版5/6章参照)、接続候補の直線が長くなることがあるが、1.3節の安全性検証(covers(safeCenterlineRegion))がこれを検出する。
WorkArea内の走行順は「offset index昇順」だけでは決まらない(進行方向の交互化・実際に安全な接続だけを候補にする必要があるため)。
ノード: WorkArea内の各RouteSegmentについて、FORWARD/REVERSEの2方向をそれぞれ1ノードとする(1つのRouteSegmentから2ノード)。
辺: 1.3節で確定した安全な接続候補を辺とする(方向を考慮するため、「AをFORWARDで終えてBをFORWARDで始める」等、具体的な方向の組ごとに辺の有無を判定する)。
経路探索: 全RouteSegmentをちょうど1回ずつ訪れる経路(各RouteSegmentは1方向だけを選ぶ)のうち、interPassConnectionsの総距離が最小のものを求める。WorkArea内のpass数は実測筆で通常小さいため、7章と同じ「候補上限付き探索+決定的tie-break」の枠組みを踏襲する(具体的な上限は7章と共通のB群技術決定事項とする、17章)。
安全な経路が1本も見つからない場合: 1.3節へ戻り、当該候補を複数pathへ再分割する。安全なpath coverとWorkArea間transferの双方を生成できない場合に限りUNSUPPORTEDとする。
開始点候補: 実務上、lateralAxis上の両端(WorkArea内で最も入口側/最も奥側にある候補列)の2つを開始点候補とし、それぞれについて経路探索を行う(中間列から開始する経路は通常の作業として不自然なため、意図的に候補から外す)。
終端lane拘束(Track #88 restart再修正、Issue #69指摘): 開始側ごとに「interPassConnectionsの総距離が最小の1件」だけへ潰すと、終端がlateralAxisの端(finishingLap3StartAnchor等への接続に必要、1.6/1.9節)ではない中間列で終わる経路だけが残り、終端が端で終わる「安全だが最短ではない」別経路が失われる場合がある(具体例: WorkArea内のあるlaneが凹形状で複数segmentに分裂している場合、開始側から全segmentを覆う安全な経路が複数存在し、そのうち中間列で終わる経路の方が端列で終わる経路より内部距離が短いことがある。該当Track記録に数値付きの反例あり)。したがって、経路探索は開始側だけでなく終端側(いずれもlateralAxisの両端に限る)との組合せごとに独立に行う。結果としてinternalPlanCandidatesは最大開始側数×終端側数(通常4件、WorkAreaが単一laneのみなら1件)を保持する。開始側から到達できる、終端が両端のいずれかである安全な完全経路が1つも無い場合に限り、終端を問わない探索(1.5節3と同じ基準)へfallbackし、その候補は終端laneが未確定である旨を明示する。
fromWorkArea→toWorkAreaをpairwiseに確定してはならない。WorkAreaが3個以上の場合、中間WorkAreaのincomingが要求する内部経路とoutgoingが要求する内部経路が衝突しうるためである。全WorkAreaについて、訪問順・各internalPlanCandidatesの選択・各PerimeterConnectionCandidateを一体のCenterTillageTraversalPlanとして評価する。
selectedInternalPlanのentryPoint/exitPointに一致する。isSafe == true)を満たす。目的関数は、(1)全ハード制約を満たす、(2)耕耘残し0、(3)中央耕終端を仕上げ3列目開始点へ一致させる、(4)総DEADHEAD距離最小、(5)多重耕耘面積最小、(6)同点ならtransfer回数最小、(7)決定的tie-breakの順とする。安全な完全被覆候補が無ければUNSUPPORTEDとする。
計画順は「奥側」という単一スカラーから決めず、仕上げ周回3列目の開始点を中央耕の終端アンカーとして固定し、そこから逆算する。終端アンカーへ接続するWorkAreaを最後に予約し、残りのWorkAreaを中央耕開始位置から安全に訪問する。周回始点の反対側に複数WorkAreaがある場合は、開始位置から経路的に手前のWorkAreaから入り、奥へ進んだ後に外周transferで最終WorkAreaへ戻る。transfer回数は原則としてWorkArea数−1回であり、列数には比例しない。
「列数が増えてもtransfer回数が列数に比例しない」ことを14章のfixtureで数値assertする。
WorkArea順序に汎用の「奥側」値は持たせない。必要なのは、中央隣接耕の最終位置を仕上げ周回3列目の開始点へ一致させることである。plannerは次の2アンカーを固定して全体経路を探索する。
centerTillageStartAnchor: 導入工程から中央隣接耕へ入る位置。finishingLap3StartAnchor: 中央隣接耕終了後に3列目周回を開始する位置。探索は終端アンカー側から逆向きに「最後に訪問可能なWorkAreaとinternal plan」を確定し、残りを開始アンカー側から接続する。開始側に複数WorkAreaが分裂している場合は、安全な接続経路上の到達距離が短い手前側を先にする。したがって、入口からの直線距離やlateralOffset最大値だけで順序を固定しない。
前版のplannedTillageTarget = normalized.geometry全体という定義では、安全余白marginMが0より大きい場合、意図的に耕さない境界帯(幅marginM)が常にremainingUntilledとして検出されてしまう欠陥があった。次の3種を分離する。
// 1. 物理境界(元の筆polygonそのもの、圃場外耕耘の検出基準)
val physicalFieldGeometry: List<LatLon> = /* = normalized.geometryのLatLon表現、Stage1 */
// 2. 通常の中央隣接耕・周回耕が耕すと想定される領域(remainingUntilledの検出基準)
val allowedTillageTarget: List<LatLon> = /* inset(normalized, marginM)の境界、下記参照 */
// 3. 作業機中心線を配置できる領域(前版のSafeCenterlineRegionと同じ、Stage4)
val safeCenterlineRegion: List<LatLon> = /* inset(normalized, h + marginM) */
図示:
physicalFieldGeometry(元の筆polygon境界)
┌───────────────────────────────────────┐
│ ← marginM(安全余白) │
│ ┌───────────────────────────────────┐ │ allowedTillageTarget = inset(physicalFieldGeometry, marginM)
│ │ ← h(作業機半幅) │ │ (通常TILL/DOUBLE_TILLのswathが到達すると想定される範囲)
│ │ ┌─────────────────────────────┐ │ │
│ │ │ safeCenterlineRegion │ │ │ = inset(physicalFieldGeometry, h + marginM)
│ │ │ (lap1中心線はこの境界上) │ │ │ (通常TILLの中心線を置ける範囲)
│ │ └─────────────────────────────┘ │ │
│ └───────────────────────────────────┘ │
│ physicalFieldGeometryとallowedTillageTargetの間(幅marginMの帯)は、通常TILLでは
│ 設計上到達しない余白。CornerTillだけがこの帯へ入りうる(2.3節)。
└───────────────────────────────────────┘
allowedTillageTargetがinset(physicalFieldGeometry, marginM)である理由: lap1の中心線はsafeCenterlineRegionの境界(深さh + marginM)上にあり、その占有幅(CAP_FLAT buffer、半幅h)の外側端は境界から深さ(h + marginM) - h = marginMに達する。したがって、通常のTILL/DOUBLE_TILL swathが到達すると設計上期待できる範囲の限界は、物理境界から深さmarginMである。
data class TillageCoverageLedger(
val physicalFieldGeometry: List<LatLon>,
val allowedTillageTarget: List<LatLon>,
val allowedCornerTillTarget: List<LatLon>, // 2.3節、新設
val perimeterTilledAreaM2: Double,
val cornerTilledAreaM2: Double,
val centerTilledAreaM2: Double, // TILLのみ
val doubleTillAreaM2: Double, // DOUBLE_TILLのみ
val combinedTilledAreaM2: Double, // perimeter+corner+center+doubleTillのunion
val intentionalOverlapAreaM2: Double,
val remainingUntilledAreaM2: Double,
val outsidePhysicalFieldTilledAreaM2: Double, // 新設: 圃場外を耕していないかの安全検査
val safetyMarginViolationAreaM2: Double, // 新設: 通常TILL/DOUBLE_TILL/周回がallowedTillageTargetを超えていないか
val remainingUntilledGeometry: List<List<LatLon>>,
val outsidePhysicalFieldTilledGeometry: List<List<LatLon>>,
)
算出式:
combinedTilledArea = perimeterTilledArea ∪ cornerTilledArea ∪ centerTilledArea ∪ doubleTillArea
ordinaryTilledArea = perimeterTilledArea ∪ centerTilledArea ∪ doubleTillArea [cornerTilledAreaを含まない]
remainingUntilledArea = allowedTillageTarget − combinedTilledArea
outsidePhysicalFieldTilledArea = combinedTilledArea − physicalFieldGeometry
safetyMarginViolationArea = ordinaryTilledArea − allowedTillageTarget [corner-tillは対象外、2.3節で別途検証]
intentionalOverlapArea = union(全ペアのpairwise intersection: perimeterTilledArea, cornerTilledArea, centerTilledArea, doubleTillArea)
outsidePhysicalFieldTilledAreaは常にゼロに近いことが必須(13章invariant)。safetyMarginViolationAreaは、通常TILL/DOUBLE_TILL/周回耕が設計上の余白へ意図せず侵入していないかを検出する(corner-tillは意図的に侵入しうるため対象外とし、2.3節で別に扱う)。
追い詰めは通常のTILL/DOUBLE_TILLより境界へ近づく(実際の角を耕すため)。したがってsafeCenterlineRegion/allowedTillageTargetをそのまま適用できない。次を分離して定義する。
allowedCornerTillTarget: CornerTillの占有幅(TILL swath)が収まるべき許可領域。allowedCornerTillTarget = physicalFieldGeometry(=安全余白は「中心線計算だけの余裕」であり、実際に機械が境界へ接することは許容する)。allowedCornerTillTarget = allowedTillageTarget(=安全余白は「機械を境界へ近づけない領域」であり、追い詰めも含めすべての耕耘がこの余白を守る。この場合、追い詰めが実際の角を耕せない筆が生じうる)。本書の推奨は、通常TILLでは安全余白を守り、CornerTillだけは安全余白帯への進入と物理境界への接触を許容する一方、作業機占有領域の物理境界外への越境は許容しない、である。17章A5でこの1件を利用者確認する。
ただし、targetVertexは追い詰め対象角の識別子であり、作業機中心線の終点ではない。中心線を凸頂点そのものまで伸ばして半幅hの対称bufferを作ると、通常は必ずpolygon外へはみ出すため、CornerTill geometryを次の要素へ分ける。
targetVertex: 対象となる凸頂点。cornerApproachLine: polygon内側から対象角へ向かう作業機中心線。cornerStopPoint: 内角・作業機半幅・approach方向から求めるpolygon内側の停止/反転点。cornerTilledSwath: CornerTillで実際に耕される横方向占有領域。remainingCornerArea: 角近傍の耕耘対象からcornerTilledSwathと外周swathを除いた残存領域。cornerStopPointは、cornerTilledSwathがphysicalFieldGeometry内に収まり、かつ外周swathとの合併で角近傍の対象領域を覆う位置として求める。90度・鋭角・鈍角で数値検証し、作業機幅では物理的に覆えない角はwarningまたはUNSUPPORTEDとする。作業機の前後形状を持たない現段階では横方向占有幅だけを保証し、前後方向の完全な角被覆は将来課題とする。
前版のWorkAreaCoverageResultをそのまま維持する(対象をallowedTillageTarget基準へ読み替える以外の変更なし)。
data class WorkAreaCoverageResult(
val coveredByPerimeterAreaM2: Double,
val remainingAreaM2: Double,
val fullyAbsorbedByPerimeter: Boolean,
)
data class PerimeterConnectionCandidate(
val lapNumber: Int,
val circulation: PerimeterCirculation,
val entryConnection: List<LatLon>, // fromWorkAreaのexitPoint候補 → lap上のentry point
val lapTravel: List<LatLon>,
val exitConnection: List<LatLon>, // lap上のexit point → toWorkAreaのentryPoint候補
val totalDistanceM: Double,
val isSafe: Boolean,
val warnings: List<NavGeometryWarning>,
)
前版からの変更: entryConnection/exitConnectionの起点・終点は、WorkAreaの「代表接続点」(前版、あらかじめ固定された1点)ではなく、**1.6節で列挙したWorkAreaInternalPlan候補のexitPoint/entryPoint**を使う。1つのWorkArea対について、fromWorkAreaのinternalPlanCandidates×toWorkAreaのinternalPlanCandidates×lapNumber(3)×circulation(2)の組み合わせを評価しうる。
Track #88 restart再修正による組合せ数の修正: 1.5節(5)の修正によりinternalPlanCandidatesの上限が「最大2件」から「最大開始側数×終端側数(通常4件)」へ変わったため、本節冒頭の組合せ数も2×2×3×2=24通りではなく、**最大4×4×3×2=96通り**として見積もる。実際に評価する候補数は、2で述べた安全性検証(entryConnection/exitConnectionのcovers(safeCenterlineRegion))を先に通した候補のみであり、96通り全てを常に生成・保持するわけではない(B群の技術決定、7章と同じ計算量管理の枠組みを適用する)。
LengthIndexedLineを再利用)fromWorkArea側候補のexitPoint(1.6節)から、対象lap ring上の最近傍点(entry point)への直線。entryConnectionがsafeCenterlineRegionにcoversされるか検証する。失敗した場合、この候補(lapNumber, circulation)は不成立とする(曲線化・迂回を自動生成しない)。LengthIndexedLine(対象lapのring、既に安全と分かっている境界)を使い、entry pointからexit point(toWorkArea側候補のentryPoint(1.6節)の最近傍点)までの部分経路を抽出する。LengthIndexedLine.extractLine(startIndex, endIndex)は開始・終了indexの大小関係により一方向の部分経路を返すため、CW/CCWの両方向を得るには、ring総延長から「もう一方の残り経路」を計算する2通りを候補として扱う。toWorkArea側候補のentryPointへの直線。entryConnectionと同じsafety checkを適用する。(lapNumber, circulation)組み合わせについてtotalDistanceM(3部の長さの合計)を計算し、PerimeterConnectionCandidateとして保持する。汎用グラフ探索を使わない理由: 接続の始点・終点候補は「WorkArea内部経路のexitPoint/entryPoint候補(1.6節、最大2種)」と「lap ring上の最近傍点」にほぼ限定でき、Dijkstra等の汎用探索が必要になるほどノード数が多くならない。entryConnection/exitConnectionが直線でsafety checkに失敗した場合、他の中央pass経由の複雑な迂回を新たに生成することは本書のスコープ外とし、その(lapNumber, circulation)候補を単純に不成立として扱う(安全な接続経路を生成できなければ、その候補は不成立)。より複雑な迂回探索が必要になるケースは、14章のfixtureで発見された場合に将来課題として扱う。
全PerimeterConnectionCandidateについて次を検証する(isSafeフィールドの算出基準)。
entryConnection/lapTravel/exitConnectionのいずれもsafeCenterlineRegionにcoversされる(圃場外を通らない、を含む)。RouteAction.actionTypeはDEADHEAD(TILLではない)。entryConnection.last() == lapTravel.first()、lapTravel.last() == exitConnection.first())。fromWorkArea側WorkAreaInternalPlan.passPlans.last()から生成されるRouteAction)の終点とentryConnection.first()が一致する。toWorkArea側WorkAreaInternalPlan.passPlans.first()から生成されるRouteAction)の始点とexitConnection.last()が一致する。いずれかを満たさない候補はisSafe=falseとし、全候補がisSafe=falseならそのWorkArea対の接続は不成立とする(4章の選択規則で、安全な候補が1つも無い場合はTillagePlanSupportLevel.UNSUPPORTED)。「from action」「to action」がWorkArea内部経路の候補と紐づいたことにより(1.6節)、生成条件(3.2節)と検証条件(本節)が同じ候補集合を参照する一貫した構造になっている。
h + marginMの深さ)であり、safeCenterlineRegionの境界そのものに一致する(9章)。安全余裕という意味では、lap1はむしろsafeCenterlineRegionの境界上にあり、それ以上外側へ逸脱する余地がない。TillagePlanInvariants.validateCirculationConsistency(境界A、baseline)は「隅の追い詰め・周回移動のcirculation」と「仕上げ周回のcirculation」が逆向きであることを要求している(#4677/#4679の確定仕様どおり)。したがって「既存のcirculationと同じ」という単一の基準は存在せず、PerimeterDeadheadTransferのcirculationは独立に評価する必要がある。各WorkArea対(fromWorkArea→toWorkArea)について、3章で生成したPerimeterConnectionCandidate(1.6節の内部経路候補との組み合わせにより最大24候補)を、次の基準で評価する。
isSafe == trueの候補のみを残す。1つも残らなければ、そのWorkArea対の接続は不成立。fromWorkArea側totalInternalDistanceM + totalDistanceM(transfer) + toWorkArea側totalInternalDistanceMの合計が最小のものを主基準として選ぶ。lapTravel)が、まだ訪問していない他のWorkAreaの接続点近傍を通過する場合、警告を付与する(ハード制約にはしない)。選択規則: 安全条件(1)を満たす候補の集合の中から、総DEADHEAD距離(2)が最短のものを選ぶ。単純な距離最短(安全性を考慮しない全候補中の最短)は採用しない。同点時のみ4のtie-breakを適用する。
本章は1〜4章(WorkArea/Coverage/Transfer/lap選択)・7章(遅延追い詰め配置)が前提にする
cornerTillTargets/deferredTargetsの基盤定義である。
enum class VertexClassification { CONVEX, CONCAVE, COLLINEAR }
data class FieldVertexInfo(
val vertexIndex: Int, // FieldShapeResult.cornersと同じindex体系
val position: LatLon,
val classification: VertexClassification,
val interiorAngleDeg: Double, // 0〜360度、180度=COLLINEARの中心
val requiresCornerTill: Boolean, // classification == CONVEXと同値(冗長だが可読性のため保持)
)
data class FieldVertexClassificationResult(
val vertices: List<FieldVertexInfo>,
val warnings: List<NavGeometryWarning>,
)
担当モジュール: FieldGeometryOpsに新設classifyVertexTurns(normalized, collinearToleranceDeg): List<VertexTurn>(internal)、FieldPlanningGeometryに新設classifyVertices(field, collinearToleranceDeg): FieldVertexClassificationResult(public)。
頂点iについて、隣接頂点prev=i-1, next=i+1(mod n、局所メートル座標)から辺ベクトルv1 = corner[i] - corner[prev]、v2 = corner[next] - corner[i]を作る。分類(凸/凹/COLLINEAR)と内角の数値の両方を、最初に巻き方向を正規化した1つの量(signedTurnDeg)から導出することで、CW格納時に凸角が270度のように誤って計算される欠陥を構造的に防ぐ。
rawTurnDeg = atan2(cross(v1, v2), dot(v1, v2)) // (-180, 180]、ring格納順そのものでの符号
ringIsCounterClockwise = signedArea(corners) > 0 // 既存FieldPlanningGeometry.signedArea(private)を再利用
signedTurnDeg = if (ringIsCounterClockwise) rawTurnDeg else -rawTurnDeg
interiorAngleDeg = 180.0 - signedTurnDeg
classification =
when {
abs(signedTurnDeg) <= collinearToleranceDeg -> COLLINEAR
signedTurnDeg > 0 -> CONVEX
else -> CONCAVE
}
requiresCornerTill = (classification == VertexClassification.CONVEX)
既存コードとの関係: FieldGeometryOps.isConvex(polygon全体が凸かどうか)とは独立に動作する。classifyVerticesは頂点ごとの局所的な性質であり、凹polygonでも全頂点に対して意味のある結果を返す。
cornerTillTargets(field) =
classifyVertices(field).vertices
.filter { it.requiresCornerTill }
.map { it.vertexIndex }
新設FieldPlanningGeometry.cornerTillTargets(field, collinearToleranceDeg): List<Int>。
TillagePlanGeometryInvariantsで検証、13章)。interiorAngleDegの数値に依存しない(5.8節のfixtureで確認)。| 箇所 | 4件固定かどうか | 判定 |
|---|---|---|
RouteActionRole.CornerTill(cornerIndex: Int, stage: CornerTillStage) |
cornerIndex: Intで任意個数を表現可能 |
固定なし |
TillagePlanInvariants.validateCornerTillUniqueness |
重複検出のみ、個数上限なし | 固定なし |
TillagePlanInvariants.validatePhaseOrder/validateRolePhaseCompatibility |
CornerTillの出現回数は無制限 |
固定なし |
CenterTillageOrientation.deferredCornerIndices |
introductionVisitOrder.drop(peelPosition + 1)、0〜n-2件を表現可能。矩形fixtureで0/1件なのは入力の性質による結果 |
固定なし(実装は既に一般化済み)。ただし現状は全頂点(凸/凹/COLLINEAR区別なし)が対象のため5.6節の修正が必要 |
TillagePlanInvariants.validateCenterTillagePassContinuityのexpected = if (size==1) [0] else [0,1] |
segmentIndex集合を{0}/{0,1}にしか許容しない |
固定あり(要修正)。{0..N}へ一般化する必要がある(17章C1) |
RouteAction.ktのKDoc(「四隅ごと」等) |
コメントのみ | 表現上の固定、実装時に更新 |
TillagePlanFixtures.ktの矩形専用fixture |
4頂点・0/1件遅延という具体的期待値 | fixtureとしては妥当、既存fixtureは変更しない |
| Track #71 record(#4706)「矩形の場合…0/1になることが判明」 | 「矩形の場合」と明記済み | 矛盾なし |
結論: (a)validateCenterTillagePassContinuityの{0}/{0,1}制限、(b)centerTillageOrientation.deferredCornerIndicesが全頂点を対象にしている点、の2点が実質的な「固定」箇所であり、17章C1/C2で承認事項とする。
centerTillageOrientationの凹対応に関する提案(既存baseline変更、要承認、17章C2)centerTillageOrientationの遠い側判定を、全頂点ではなく**cornerTillTargets(凸かつCOLLINEARでない頂点)のみ**を対象に行うよう一般化することを提案する。Track #71(#4705)が懸念した「凹みの頂点が誤ってfarExtremeとして選ばれる」問題は、凹頂点を候補から除外すれば構造的に発生しない。既存baselineへの変更のため実装しない。未承認のままでは、凹多角形はcenterTillageOrientationが引き続きnullを返すため、14章の分類で常にUNSUPPORTED(理由: NonConvexOrientationUnsupported)に留まる。
peelPosition = entrance.introductionVisitOrder.indexOf(orientation.peelCornerIndex)
duringInitialLoopTargets = cornerTillTargets(field) ∩ entrance.introductionVisitOrder[0 .. peelPosition]
deferredTargets = cornerTillTargets(field) ∩ entrance.introductionVisitOrder[(peelPosition + 1)..]
5.6節が採用された場合、既存deferredCornerIndices自体がdeferredTargetsと一致する。未採用の場合はTillagePlanComposer側でdeferredCornerIndices ∩ cornerTillTargetsを計算して使う。各deferredTargets頂点への中断位置は、7章のアルゴリズムで確定するpassの直線への垂線の足とする。
頂点分類の基本fixture(凸五角形・凸六角形・凹五角形・複数凹角polygon・細分点を含むpolygon)に加え、同一polygonの(a)元の頂点順、(b)CW/CCW反転、(c)開始頂点の循環移動の3パターンで、classificationとinteriorAngleDegが数値として完全一致することを検証する(14章)。
| ID | 意味 | 決定方法 |
|---|---|---|
candidateLaneId |
幾何的に同じ候補中心線(offsetの列そのもの) | offset indexから決定的に導出(既存CandidateRoute.idForと同じ考え方) |
workAreaId |
連続して耕す中央作業領域(1章) | Stage 10のグルーピング結果、決定的な採番順序(例: lateralOffsetが小さい順) |
passId(RouteActionRole.CenterTillagePass.passId、既存。WorkAreaPassPlan.passIdと対応、1.2節) |
TillagePlan.actions内で実際に連続して走行するpass |
6.2節の対応規則 |
segmentIndex(既存) |
同一passIdを遅延追い詰め(CenterTillageDetour)で中断・復帰した区間順 |
3.5節相当(前版)、複数回対応に一般化済み |
candidateLaneId→passIdの対応規則同一candidateLaneIdのRouteSegment群がすべて同じworkAreaIdに属する場合、それらは同一passIdを持ち、segmentIndexは遅延追い詰めによる中断・復帰の区間順を表す。
同一candidateLaneIdのRouteSegmentが異なるworkAreaIdに分かれて属する場合、各workAreaId内の部分ごとに別々のpassIdを割り当てる。
理由: 既存TillagePlanInvariants.validateCenterTillageInterruptionは、segmentIndexの連番が「CenterTillageDetour(TO_CORNER) → CornerTill(DEFERRED) → CenterTillageDetour(RETURN_TO_INTERRUPTION)」というサンドイッチ構造で中断・復帰することを検証する契約になっている(境界Aのbaseline)。WorkArea間の移動はPerimeterDeadheadTransferという別のroleであり、この既存invariantが期待するサンドイッチ構造とは異なる。したがって、WorkArea間で分かれる場合は別passIdとし、PerimeterDeadheadTransferが2つの独立したCenterTillagePass(それぞれ別passId、各自segmentIndexは0始まりで独立)を接続するという構造にする。
TillagePlanInvariants.validateCenterTillagePassContinuity(5.5節で一般化した{0..N}版)は、passIdごとにsegmentIndex集合を検証する。6.2節の規則により、WorkArea間で分かれたcandidateLaneIdは最初から別passIdとして記録されるため、この既存invariant(一般化後)はそのまま適用でき、WorkArea間の移動を誤って「同一passの中断」として検証してしまうことはない。WorkArea間transferを挟んだ離れたactionを、単純に同じpassの連続segmentとして扱わない。
deferredTargets、5章の定義)をちょうど1回処理する。TO_CORNER/RETURN_TO_INTERRUPTION(DEADHEAD)の中心線がphysicalFieldGeometryにcoversされる。CornerTill(TILL)の中心線がphysicalFieldGeometryにcoversされる。中心線終点は対象頂点そのものではなく、2.3節のcornerStopPointとする。CornerTillの作業機横方向占有領域(CAP_FLAT buffer、半幅h)**をphysicalFieldGeometryと比較し、越境量をpreviewへ表示する。安全余白帯への進入と境界接触は許容し、筆polygon自体の誤差を考慮して計算上の越境だけで計画を一律拒否しない。segmentIndexが連番である。各deferredTargets頂点について、次の順で候補を処理する(前版の「近い順にK件だけ生成」という処理順を撤回する)。
WorkAreaPassPlan(1章)を対象に、その頂点への垂線の足を中断位置候補とする(件数を絞らない)。K件へ絞る(Kは安全性検証を通過した候補集合の中での最適化計算量を抑えるための上限であり、安全な候補を見落とす上限ではない)。この順序により、「距離が近い3候補は不安全、4番目だけ安全」という場合でも、4番目の候補が安全候補集合に含まれた上で正しく評価される。
前版の手順(候補0件ならUNSUPPORTED、候補1件なら確定、複数残る場合のみ総DEADHEAD距離を最小化する組み合わせを全探索、同点はtie-break規則)を維持する。入力が「距離上位K件」ではなく「安全性検証を通過した上位K件」になった点のみ異なる。
候補数上限K・全探索対象数上限D_maxは、本書では固定値を決定しない。7.4節(前版)で提示したK=3/D_max=16は、3^16 ≈ 4,300万通りであり「小さいので十分」と扱える根拠がなかった(#4726指摘のとおり)。これらはfixtureだけでなく実測(計算量・メモリ・代表fixtureでの実行時間)に基づいて決定する技術事項とし、17章B群へ位置づける。
計算量を抑える主な方策として、次を比較検討する(実装時に測定結果とあわせて採否を決める)。
| 方策 | 概要 | 期待効果 |
|---|---|---|
| WorkAreaごとの分割(推奨候補) | 遅延追い詰め対象の候補passは、通常その対象頂点に近いWorkArea内のpassに限られる(遠いWorkAreaのpassを候補にする理由がない)。WorkAreaごとに独立した部分問題として全探索する | 全体のDを、WorkAreaごとの小さなD_workAreaへ分割でき、K^DからΣ K^(D_workArea)へ計算量を削減できる |
| passごとの独立分割 | 各deferred対象の候補passが互いに重ならない(排他的)場合、対象同士は独立に最適化できる | 候補が重ならない範囲でさらに計算量を削減 |
| branch and bound | 部分解の下界(すでに確定した距離)が現在の最良解を超えた時点で枝刈りする | 全探索よりも実用的な平均計算量 |
| 動的計画法 | passの物理的走行順に沿った状態(直前までの割当)を状態space DPで持つ | 状態数が管理可能なら多項式時間 |
| 制約充足問題(CSP)としての定式化 | ハード制約(セグメント順・安全性)を制約として明示し、既存のCSPソルバー相当の手法(バックトラッキング+forward checking)を使う | 実装の見通しが良く、ハード制約の網羅性を保証しやすい |
| 時間/展開ノード数上限付き探索 | 一定時間・ノード数で打ち切り、その時点の最良解(全ハード制約を満たすもののみ)を採用する | 最悪ケースの実行時間を有限に保証できる |
本書の推奨: まずWorkAreaごとの分割を適用し(deferred対象の候補生成自体をそのWorkArea内のpassに限定する、7.2節の「全passから」は「そのWorkArea内の全passから」と読み替えてよい妥当性がある)、各WorkArea内の部分問題が依然として大きい場合にのみbranch and bound等を追加で適用する、という段階的な方針を推奨する。ただし最終的なK・分割方針・打ち切り条件の具体的な値は、代表fixture(9章相当)での実測結果を踏まえて実装時に確定する(17章B群)。
D_max超過時・探索打ち切り時のfallback(ハード制約を破らない、#4726指摘(8))いかなる打ち切り・fallback方式を採用する場合も、次の手順を守る。
UNSUPPORTEDとする。前版はStage 10〜16の16段階だったが、本改訂ではWorkAreaグルーピング(旧Stage10)を「グルーピング」と「内部経路探索」の2段階へ分割し、coverage・transferの順序を1章・2章の内容に合わせて再整理したため、全体で17段階になる。
Stage 1 正規化
Stage 2 頂点分類・追い詰め対象抽出(5章)
Stage 3 対応形状判定
Stage 4 安全な中心線走行可能領域(safeCenterlineRegion)
Stage 5 外周3列・中央隣接耕領域分離(CenterTillageRegion、Stage9専用入力)
Stage 6 横断方向の利用可能span算出
Stage 7 重ね幅solver
Stage 8 候補列生成
Stage 9 polygon交差・区間分裂評価(candidateLaneIdごとのRouteSegment群)
Stage 10 WorkAreaグルーピング(1.3節、安全な接続候補を条件にする)
Stage 11 WorkArea内部経路探索(1.5節、候補ごとのWorkAreaInternalPlan)
Stage 12 coverage ledger算出(2章、physicalFieldGeometry/allowedTillageTarget/allowedCornerTillTarget基準)
Stage 13 WorkArea間transfer・内部経路の同時確定(1.6節・3章・4章)
Stage 14 遅延追い詰め配置(7章、WorkArea単位分割を含む)
Stage 15 全体composer → TillagePlan
Stage 16 幾何invariant検証
Stage 17 PC/Tablet preview
Stage 10/11とStage 12の順序: WorkAreaグルーピング(Stage 10)とその内部経路探索(Stage 11)は、coverage判定(Stage 12)を必要としない(純粋な空間的接続性の問題)。coverage判定(Stage 12)は、外周・追い詰めのTILL領域(既知)と中央領域のTILL占有領域(Stage 10/11で確定したWorkArea構成、ただし訪問順序=どちらのWorkAreaを先に完了するかはcoverage判定に影響しない、集合としての占有領域だけが必要)から計算できる。Stage 13(transfer・内部経路の最終確定)は、Stage 12で「どのWorkAreaがそもそも外周吸収により不要になるか」を確定してから行う(外周吸収で完全に不要になったWorkAreaはtransferの対象から外れる)。
RouteAction.geometryは作業機中心線を表す。既存RouteAction.geometry(緯度経度の折れ線)が「アンテナ位置」と「作業機中心」のどちらを表すかは境界A/B-1に明記が無いが、手順8(docs/runbooks/navigation-pc-closed-loop-implementation.md)が「ライブ作業機中心」を案内対象と比較すると書いていることから、この前提を採用する。safeCenterlineRegion、追い詰め(CornerTill)はallowedCornerTillTarget(2.3節、要利用者確認)に収まることを検証する。TILL時の横方向占有幅にはimplementWidthCmを使う。DEADHEAD時は作業機を上げて移動するため、本来必要なのはmax(tractorWidthCm, raisedImplementWidthCm)である。既存Profile/ProfileSnapshotを調査した結果、現状はimplementWidthCmしかなく、車幅・移動時外幅は保持していない。
このため、レイヤー0設定候補としてdeadheadClearanceWidthCmを追加する。値が確定・設定されるまでは、WorkArea内列間接続、PerimeterDeadheadTransfer、TO_CORNER/RETURN_TO_INTERRUPTIONについて保証できるのは中心線が許可領域内にあることまでとし、実車横幅の安全を保証したと表示してはならない。値が設定された後は、全DEADHEAD geometryを半幅deadheadClearanceWidthCm / 2でbufferし、安全領域内に収まることを共通invariantで検証する。
buffer(h, endCapStyle=CAP_FLAT)による占有幅検証は、RouteAction.geometryの線分に対して**左右(横方向)**にのみhだけ広げた領域が許可領域内に収まるかの検証であり、作業機の前後方向の寸法を一切表現していない。列の端点(前後方向)で作業機が実際にどこまで耕耘・占有するかは本書のパイプラインでは保証しない。将来課題: 列端の前後方向占有を保証するには、作業機の前後寸法またはアンカー位置をProfileSnapshotへ追加する必要がある。本書はこれを実装対象に含めない。
筆polygon境界(physicalFieldGeometry) : Field.polygons[].shell / holes(WGS84緯度経度)
作業機幅 W : ProfileSnapshot.implementWidthCm [cm]
作業機半幅 h : W / 2 [cm] → h / 100 [m]
基準重ね幅 O_base : ProfileSnapshot.overlapWidthCm [cm]
実際の重ね幅 O_actual : OverlapAdjustment.actualOverlapCm [cm]
実効列間隔(pitch) P : OverlapAdjustment.actualPitchCm [cm]
安全余白 marginM : 初期値0cm、レイヤー0で変更可能。計画拒否の絶対境界ではなくpreview上の目安
実効列間隔 P = 作業機幅 W − 実際の重ね幅 O_actual (既存WorkPitch契約と同一)
3段階(lap1, lap2, lap3)+ 中央隣接耕領域(CenterTillageRegion)の境界を求めるための1段階、計4段階のinsetRings呼び出しで足りる。各段階は前段階からの累積深さで表現できるが、insetRingsは毎回physicalFieldGeometryから絶対深さで呼び出す(相対計算による誤差の累積を避ける)。
lap1中心線 = insetRings(physicalFieldGeometry, h + marginM)の境界
lap2中心線 = insetRings(physicalFieldGeometry, h + marginM + P_perimeter)の境界
lap3中心線 = insetRings(physicalFieldGeometry, h + marginM + 2 * P_perimeter)の境界
CenterTillageRegion境界 = insetRings(physicalFieldGeometry, h + marginM + 2 * P_perimeter + h)の境界
P_perimeter(外周列同士のピッチ)は基準ピッチ(baselinePitchM = W - O_base)を用いる(9.1節で確定)。
insetRingsはJTS bufferの結果がMultiPolygonになりうることをそのままList<Polygon>として返す。分裂した場合はNavGeometryWarning.SafeRegionSplit/CenterRegionSplitを付与し、個別の閉じたリング/独立した中央領域として扱う(自動的に1つへマージしない)。buffer(-depth)の結果が空Geometryになった場合(SafeRegionEmpty/CenterRegionEmpty)、FieldPlanningGeometryの該当関数は空リストを返し、呼び出し元は「中央隣接耕は成立しない」として扱う(13章fallback整理参照)。
候補列の交差結果のうち、長さが閾値(既存NavGeometrySettings.minSegmentLengthM、既定1.0m)未満のものはShortSegment警告を付け、通常列として採用しない。
JOIN_MITREを既定とし、mitreLimitはJTSの既定値5.0を暫定技術値として使う(9.1節、17章B6)。理由は、耕耘計画の角は実際の筆の角をできるだけ保持すべきであり、JOIN_ROUNDによる丸めはfixtureのnumeric assertionを複雑にするため。
既存DEFAULT_FIELD_GEOMETRY_TOLERANCE_M = 0.1(FieldPlanningGeometry.kt)を、Stage 3〜9全体の既定許容誤差として再利用する。新しい許容誤差定数は導入しない。
actualPitchCm = round(D * 100 / (N - 1)) [N >= 2]
actualOverlapCm = implementWidthCm - actualPitchCm
adjustmentCm = actualOverlapCm - baselineOverlapCm
endpointResidualCm = round(D * 100) - actualPitchCm * (N - 1)
Dは開始側中心線からlap3への理想合流点までの距離(m)、Nは中央隣接耕の候補列数。endpointResidualCmは、整数cmへ丸めたことで生じる理想終端との差分。
OverlapAdjustmentへの追加(既存baseline変更、要承認、17章C3)data class OverlapAdjustment(
val baselineOverlapCm: Int,
val actualOverlapCm: Int,
val adjustmentCm: Int,
val laneCount: Int,
val laneCountParity: OverlapParity,
val actualPitchCm: Int,
val endpointResidualCm: Int, // 新設
val fallbackApplied: RouteActionType?,
)
laneCountParity)を満たす。|endpointResidualCm|が最小。固定の不許可閾値は設けず、実値をpreviewへ表示する。|adjustmentCm|)が最小。adjustmentCm > 0を優先)。laneCount)が少ない側。round(四捨五入)を暫定提案のまま維持する(17章B4)。
CenterTillageRegionが空): fallbackではなく非対応。N=1(1列だけ成立): その1列を通常耕として実行し、fallbackへ送らない。TillagePlanningSettings.defaultFallback(既定DEADHEAD)による空走り/二重耕耘fallbackの対象とする。外周吸収(2章)とfallbackは異なる概念として区別する。外周吸収は「中央列の未耕部分を外周のTILL範囲で耕す」ため、fallbackは「列数・重ね幅の帳尻を合わせる」ためであり、目的が異なる。両者が同一plan内で共存することも許容する。
前版の「DegenerateEdge(辺長閾値)」を撤回し、5章の頂点分類(角度ベースのCOLLINEAR判定)へ置き換える。
| ケース | 判定方法 | 扱い |
|---|---|---|
| 重複点・許容誤差内の実質同一点 | 隣接頂点間距離がDEFAULT_FIELD_GEOMETRY_TOLERANCE_M未満 |
FieldGeometryOps.normalize段階の既存JTS処理に委ねる。頂点分類の対象から除外 |
| ゼロ長辺 | 同上 | 同上 |
| 一直線上の細分点 | 5.2節の角度ベース分類(interiorAngleDegが180度に近い) |
COLLINEAR、追い詰め対象から除外するが元のpolygonからは削除しない |
| 実際の凸角/凹角 | 5.2節の角度ベース分類 | CONVEX/CONCAVE |
| 細い突出部 | 頂点単位ではなく、安全領域生成(10章)でのSafeRegionSplit検出 |
頂点分類とは別レイヤーの警告 |
| 自己交差 | 既存FieldGeometryOps.normalizeのbuffer(0)修復とその失敗時のInvalidPolygon |
変更なし |
| 極小面積polygon | normalized.geometry.areaが閾値未満(17章B5、要利用者決定) |
新設警告(名称未定) |
入力polygonを破壊的に簡略化しない: classifyVerticesはあくまで分析用の注釈(metadata)を頂点ごとに付与するだけであり、NormalizedField.geometry(JTS Geometry)からCOLLINEAR頂点を除去することはしない。元のpolygonはpreview表示・境界逸脱の安全判定(9章)に必要であり、簡略化すると実測データとの対応が失われる。COLLINEAR頂点はStage 9(polygon交差)の計算そのものには影響しない(JTSのintersection演算は縮退頂点があっても正しく動作する)。頂点分類はcornerTillTargetsの抽出という1箇所でのみ使われるフィルタである。
前版の表に、本改訂の変更を反映する。
| invariant | 検証層 | 実装 |
|---|---|---|
TILL時の作業機占有幅がsafeCenterlineRegion外へ出ない(通常pass) |
幾何 | TillagePlanGeometryInvariants |
CornerTill時の作業機占有幅がallowedCornerTillTarget外へ出ない(新設、2.3節) |
幾何 | TillagePlanGeometryInvariants、基準は17章A群の確定後に定まる |
圃場外と計算された耕耘量(outsidePhysicalFieldTilledArea)を算出・警告表示する。自動運転ではないため単独では計画拒否しない |
幾何+表示 | TillagePlanGeometryInvariants/preview |
通常TILL/DOUBLE_TILL/周回がallowedTillageTargetを超えない(safetyMarginViolationArea、新設) |
幾何 | TillagePlanGeometryInvariants |
WorkArea内の接続(interPassConnections)がsafeCenterlineRegion内・圃場外を通らない(新設、1章) |
幾何 | TillagePlanGeometryInvariants |
deadheadClearanceWidthCm設定時、全DEADHEAD占有幅が安全領域内。未設定時は中心線保証のみであることをwarning/previewへ明示 |
幾何+表示 | TillagePlanGeometryInvariants、9.2節 |
WorkArea内経路のentryPoint/exitPointが、対応するtransferのentryConnection/exitConnectionと一致する(新設、1.6節・3章) |
構造+幾何 | TillagePlanGeometryInvariants |
| 頂点分類・追い詰め対象が頂点開始位置・巻き方向・interiorAngleDegの数値に依存しない | 幾何 | 変更なし |
| 凹頂点・COLLINEAR頂点へCornerTillを生成しない | 幾何 | 変更なし |
| 全追い詰め対象が重複なく一度ずつ処理される | 構造+幾何 | 変更なし |
WorkArea間の移動は必ずPerimeterDeadheadTransferで表現される |
構造+幾何 | 変更なし |
PerimeterDeadheadTransferのcirculationが独立に評価されている |
構造 | 変更なし |
| DEADHEADとTILLを混同しない | 構造 | 変更なし |
| 空geometry、1点geometryを生成しない | 構造 | 変更なし |
| 短すぎる列を通常列として採用しない | 幾何 | 変更なし |
未耕部検出はallowedTillageTarget基準(2.2節で修正) |
幾何 | remainingUntilledAreaM2が閾値以下 |
| 意図された重複は異常としない | 幾何 | 変更なし |
| 許容範囲を超える重ね幅を採用しない | solver | 変更なし |
| 終端残差が許容値を超えない | solver | 変更なし |
| fallbackを通常解より優先しない | solver | 変更なし |
| 追い詰め中断後は同じpass・同じ位置へ復帰する | 構造 | 変更なし |
| 仕上げ周回3→1→2と退出方向を維持する | 構造 | 変更なし |
| 遅延追い詰めの割当が7章の決定的アルゴリズムの出力と一致し、かつ全ハード制約を満たす(7.5節で強化) | 構造+幾何 | fixtureでの回帰確認、D_max超過fallback後も全制約再検証 |
| WorkArea path coverが全passをちょうど1回含み、各WorkArea内部が安全接続だけで構成される(新設、1.3〜1.5節) | 構造+幾何 | fixtureでの回帰確認 |
前版のfixture一覧に、本改訂で追加する項目を反映する。
| fixture | 数値assert |
|---|---|
隣接列のtravelAxis投影が重なるが安全な接続が作れない形状(局所的な狭窄) |
投影重複はあるが辺が作られず、WorkAreaが分裂する |
lateralAxis/travelAxisを取り違えると誤判定になる非正方形の矩形(縦横比が大きい) |
正しい軸を使った場合の期待結果と一致 |
| fixture | 数値assert |
|---|---|
| 列長が大きく異なる台形突出部 | boustrophedon接続が安全に生成され、進行方向が交互になる |
| 中間列からの開始が短距離に見えるが両端開始の方が安全な形状 | 1.5節のアルゴリズムが両端候補から正しい方を選ぶ |
| 安全接続graphは連結だが単一Hamiltonian経路を持たない形状 | 安全なpath coverへ分割され、複数WorkAreaとしてtransfer候補が評価される |
| WorkAreaが3個ある形状 | 中間WorkAreaの同一internal planがincoming/outgoing双方と連続し、pairwise選択の矛盾がない |
| 仕上げ周回始点の反対側に複数WorkAreaがある形状 | 開始アンカーから手前側を先に訪問し、最後の中央pass終点が仕上げ3列目開始アンカーへ一致する |
| fixture | 数値assert |
|---|---|
marginM > 0の通常矩形 |
remainingUntilledAreaM2 ≈ 0(前版の欠陥=境界帯の誤検出が解消されていることを確認) |
| 圃場外を意図せず耕してしまう境界ケース | outsidePhysicalFieldTilledAreaM2 ≈ 0 |
| CornerTillが安全余白帯へ入る形状 | allowedCornerTillTargetの採用案に応じた期待値(17章A群確定後に具体化) |
| fixture | 数値assert |
|---|---|
| 内角90度・鋭角・鈍角の凸頂点 | cornerStopPointはpolygon内、swathは物理境界外へ出ず、外周swathとの合併で角近傍を覆う |
| 同じ角を回転・CW/CCW反転した形状 | 物理的に同じstop point・coverageになる |
| 異なる作業機幅 | 幅に応じてstop pointが変化し、越境しない |
deadheadClearanceWidthCm未設定/設定済み |
未設定時は中心線保証のみのwarning、設定済みでは全DEADHEAD占有幅が安全領域内 |
| fixture | 数値assert |
|---|---|
| 近距離3候補が不安全、4番目のみ安全な形状 | 7.2節の順序で4番目の候補が正しく採用される(前版の順序では見落とされることも回帰確認する) |
D'が大きい人工fixture(WorkArea分割の効果測定用) |
WorkAreaごとの分割で計算量が実測される、全体最適でなくても全ハード制約を満たす |
その他(頂点分類、中央列分裂、数値assert一覧)は前版から変更なし。
前版のlayer一覧に、次を追加する。
| kind | 内容 |
|---|---|
travelAxisProjection / lateralAxisProjection |
軸投影の可視化(デバッグ用) |
workAreaInternalPlan |
選択されたWorkAreaInternalPlanの経路・方向矢印 |
allowedTillageTarget / allowedCornerTillTarget |
2章の許可領域(半透明の別色) |
safetyMarginViolationArea |
新設安全検査、あれば警告表示 |
その他のlayerは前版から変更なし。
前版の分割案を維持しつつ、境界の対象範囲を更新する。
| 境界 | 実装対象 | 第4版での変更点 |
|---|---|---|
| B-2a 頂点分類・追い詰め対象抽出 | 5章 | 変更なし |
| B-2b 安全領域・外周offset | 10章 | 変更なし |
| B-2c 利用可能span算出 | — | 変更なし |
| B-2d 重ね幅solver | 11章 | 変更なし |
| B-2e 中央列生成・polygon交差 | Stage 8/9 | 変更なし |
| B-2f WorkAreaグルーピング・内部経路探索 | Stage 10/11(1章) | 安全接続graphのpath cover、WorkArea分割、内部経路探索を追加 |
| B-2g coverage ledger | Stage 12(2章) | 3分離(physicalFieldGeometry/allowedTillageTarget/allowedCornerTillTarget)、CornerTill専用領域を追加 |
| B-2h transfer・WorkArea全体同時評価 | Stage 13(1.6節・3章・4章) | 全WorkAreaの訪問順・internal plan・transferを一体で決定 |
| B-2i 遅延追い詰め配置 | Stage 14(7章) | 候補生成順序の修正、D_max fallbackのハード制約再検証を追加 |
| B-2j 全体composer・幾何invariant | Stage 15/16 | invariant追加(13章) |
| B-2k PC可視化 | Stage 17 | layer追加(15章) |
| B-2l Tablet previewへの受け渡し確認 | — | 変更なし |
| # | 論点 | 影響範囲 |
|---|---|---|
| A1 | 確定済み: 安全余白の初期値は0cm。レイヤー0で変更可能だが、計画拒否の絶対境界ではなくpreview上の目安 | 9章、Stage 4全体 |
| A2 | 確定済み: 中央領域が1列だけなら、その1列を通常耕する | 11章、13章 |
| A3 | 確定済み: 終端残差に固定の最大許容値を設けない。絶対値最小の計画を選び、実値をpreviewへ表示する | 11章 |
| A4 | 確定済み: 「奥側」という単一値は使わず、仕上げ3列目開始点を終端アンカーとして逆算する。開始側に複数WorkAreaがあれば安全経路上の手前側から訪問する | 1.9節。利用者判断済みのため実装時の確認項目へ移す |
| A5(確定済み) | 通常TILLでは安全余白を目安にする。CornerTillは安全余白帯への進入と物理境界への接触を許容する。計算上の越境は警告表示するが、筆polygon誤差を考慮して一律に計画拒否しない | 2.3節、7.1節 |
| A6(確定済み) | deadheadClearanceWidthCmは任意設定。未登録時は作業機幅を参考値としてpreviewし、中心線保証に限定する。車幅/上昇時外幅登録時は大きい方を使用 |
9.2節 |
| # | 論点 | 状態 |
|---|---|---|
| B1 | 外周DEADHEADで使う列・circulationの選び方 | 4章のアルゴリズムで確定済み、fixtureでの妥当性検証のみ残る |
| B2(修正) | 遅延追い詰め候補数上限K、全探索対象数上限D_max、探索方式(WorkArea分割/branch and bound/DP/CSP/時間・ノード数上限) |
固定値を決定しない。計算量測定・メモリ測定・代表fixtureでの実行結果に基づき実装時に決定する(7.4節)。K=3/D_max=16という前版の暫定値は撤回する |
| B3(修正) | WorkArea内部経路探索の候補上限 | 7章のB2と同じ枠組みで技術決定する(1.5節) |
| B4 | actualPitchCmの端数処理 |
四捨五入(暫定) |
| B5 | 外周吸収判定・COLLINEAR判定・極小面積判定の各種閾値 | 未提案 |
| B6 | JOIN_MITRE/mitreLimit | 5.0(確定済みの暫定技術値) |
| # | 変更対象 | 内容 |
|---|---|---|
| C1 | TillagePlanInvariants.validateCenterTillagePassContinuity(Track #70 baseline) |
segmentIndex集合の制約を{0..N}(N=中断回数)へ一般化する(5.5節) |
| C2 | FieldPlanningGeometry.centerTillageOrientation(Track #71 baseline) |
遠い側判定の対象をcornerTillTargetsへ限定する(5.6節) |
| C3 | OverlapAdjustment(Track #69系決定) |
endpointResidualCmフィールド追加(11.2節) |
| C4 | RouteActionRole/TillagePhase/TillagePlanInvariants(境界A baseline) |
PerimeterDeadheadTransferrole新設、TillagePhase.CENTER_TILLAGEへ属させ、circulation必須role一覧へ追加する |
| C5 | TillagePlanInvariants.validateCirculationConsistency(境界A baseline) |
PerimeterDeadheadTransferを、追い詰め/仕上げ周回のcirculation一貫性検証グループから除外し、独立に扱う(4章) |
| C6 | TillagePlanningSettings/レイヤー0設定 |
DEADHEAD安全検証用deadheadClearanceWidthCmを追加する。未設定を許容し、その場合は中心線保証のみとする(9.2節) |
B/C群が未承認・未測定の場合、対応する境界(16章)には着手しない。