FlightPlinningService 業務ロジック仕様
本ドキュメントは、飛行計画機能の業務ロジック仕様を定義する。
10月デモ向けに制限した機能については「forDemo」を記載する。
2. 機能概要
Section titled “2. 機能概要”飛行計画機能は、Operatorが入力した飛行計画をUTMに登録・更新・削除し、登録された飛行計画をDIPS通報する機能である。
下記の機能をAPIで提供する。
- 飛行計画仮登録
- POST /api/v1/fp/flight-plans
- 飛行計画一覧取得
- GET /api/v1/fp/flight-plans
- 飛行計画詳細取得
- GET /api/v1/fp/flight-plans/{flightPlanId}
- 飛行計画本登録
- POST /api/v1/fp/flight-plans/{flightPlanId}/registration
- 飛行計画更新
- PUT /api/v1/fp/flight-plans/{flightPlanId}
- 飛行計画削除
- DELETE /api/v1/fp/flight-plans/{flightPlanId}
- 飛行計画通報
- POST /api/v1/fp/flight-plans/{flightPlanId}/report
- 飛行計画周辺データ更新
- POST /api/v1/fp/flight-plans/{flightPlanId}/nearby-data-refresh
3. 用語定義
Section titled “3. 用語定義”| 用語 | 意味 |
|---|---|
| UTM (ドローン運行管理システム) | 通報先のDIPS、飛行のためのGCSと連携し、UASのオペレーター向けに飛行計画通報の支援を提供する。また、飛行計画が他の飛行計画と重複した際に、他UTM/ビジターと調整して重複を解消することを支援する。 |
| DIPS (ドローン情報基盤システム) | 日本国内でドローンを飛行させるために必要な「機体登録」「飛行許可・承認申請」「飛行計画の通報」などの手続きを、オンライン上で一元管理できるシステム。 |
| 飛行計画 | 航空法で定められた「特定飛行」を行う際に、あらかじめ飛行の日時、場所(経路)、高度、目的、操縦者などの詳細情報を国土交通省が運用するDIPSを利用して通報する制度。 |
| 空域制限 | 空港周辺・人口集中地区・小型無人機等飛行禁止法のレッドゾーン/イエローゾーン・条例による指定区域など、飛行が制限される空域。空域制限ドメイン(別スキーマ)が管理し、飛行計画本登録時およびACCEPTED以降の飛行計画更新時に競合判定の対象となる。DIPSでは「飛行禁止エリア」と呼ばれる。 |
| 運航調整(Coordination) | 飛行計画が重複したときに、重複解消するために重複した人と重複された人の間で調整すること。 |
| 周辺の飛行計画 | 対象の飛行計画の飛行領域から100mまでの範囲(バッファ)と、その飛行時間帯に重なる他の飛行計画。DIPSの飛行計画参照API(範囲指定検索)で収集する。 |
| DIPS収集飛行計画 | DIPSから収集した周辺の飛行計画を、UTM内部の飛行計画とは別に管理する独立モデル(DIPS_FLIGHT_PLAN)。自組織が通報した計画は除外するため、同一UTM上の他組織の計画を含む「自組織以外の計画」が対象となる。 |
| Operator (運航者) | UTM管理の元、ドローンを運航させる人。飛行計画作成・調整や動態監視を担う。運航調整を行う際は、依頼者、または調整対象者となる。 |
| Organization Manager (組織管理者) | Operatorが所属する組織の管理者。組織のメンバー管理を担う。Operatorが可能な操作に加えて、組織管理のための操作ができる。 |
| Visitor (ビジター) | 飛行計画調整のため、一時的にUTMを利用するドローンを運航させる人。運航調整を行う際は、依頼者、または調整対象者となる。 |
| System Operator (運用管理者) | UTMサービスの運用を担う人 |
| UTM Backends | UIと繋がり飛行計画管理、制限空域、運航調整などを担うWeb Backend Server |
4. forDemo制約事項
Section titled “4. forDemo制約事項”forDemoの対象外となるユースケースについては、flight-plan-usecase.mdを参照すること。
4.1. 飛行計画仮登録における一時保存機能の非対応
Section titled “4.1. 飛行計画仮登録における一時保存機能の非対応”forDemoでは一時保存(nameなど一部項目のみを入力した下書き状態での保存)機能を提供しないため、飛行計画仮登録(POST /api/v1/fp/flight-plan)のリクエストボディは、「飛行計画本登録」(5.1 手順2)と同一のDIPS必須項目(name/flightPurposes/departurePoint/flightPeriod/flightSpec/flyRoute/destinationPoint/riskMitigation/pilotInfo/email)をすべて入力必須とする。
ただし、飛行計画ステータスFLIGHT_PLAN.statusは本来の設計と同様に仮登録時点でDRAFTを経由したうえで、本登録(POST /api/v1/fp/flight-plans/{flightPlanId}/registration)によりACCEPTEDに遷移する構成は変更しない。Operatorからは一連の「保存」操作として提供し、内容が不完全な状態のまま明示的に保存すること(真の一時保存)はできない。
4.2. 飛行計画一覧取得のフィルター・ページング機能
Section titled “4.2. 飛行計画一覧取得のフィルター・ページング機能”forDemoでは飛行計画一覧取得のフィルター条件は飛行計画登録において必要最小限の対応とする。 また、ページング・ソート機能については対応せず、絞り込み条件に合致した飛行計画は常に登録日時の昇順ですべて返却する。
4.3. forDemoで未対応の提供API
Section titled “4.3. forDemoで未対応の提供API”以下の機能はforDemoでは対応せず、実装対象外とする。
- 経路/エリア設定・変更時の事前競合チェック(保存前のプレビュー表示)
- GCSの飛行ミッションファイルをインポートして飛行計画を作成する機能
- Operator操作による飛行禁止エリアの最新化(DIPSからの飛行禁止エリア情報の再取得)
- DIPSからの許可・承認情報取得
- 機体・操縦者のCRUD操作(飛行計画登録にて指定する機体・操縦者は事前にデータベースへ登録済みのIDを指定する方針とする)
4.4. 飛行計画周辺データ更新における取得件数の打ち切り非対応
Section titled “4.4. 飛行計画周辺データ更新における取得件数の打ち切り非対応”DIPSの飛行計画参照APIには返却件数の上限があり、上限を超えた場合は応答が打ち切られる。打ち切りの検知は 応答の件数項目との差分で可能だが、forDemoでは打ち切りが発生する規模を想定しないため対応せず、 上限を超えないケースのみを対象とする(flight-plan-er.mdの 「取得漏れ(DIPS側の返却上限)はデータモデルに持たない」および「重複計画は項番1の一覧に必ず含まれる前提とする」参照。 上限値は同節を正とする)。
4.5. 収集した飛行計画の認定USP情報はダミーの固定値とする
Section titled “4.5. 収集した飛行計画の認定USP情報はダミーの固定値とする”DIPS_FLIGHT_PLAN.certification_usp_name・certification_usp_code(認定USP名称・認定USPコード。
DIPS2.0ガイドライン飛行計画参照APIの項番20・21)は、10月デモの通報先である模擬DIPSの応答に項目自体が
存在せず取得できない(tests/mocks/mock-dips/flightplan-spec.mdの04レスポンス表)。
forDemoでは収集時にダミーの固定値を設定する。値はcertification_usp_nameを
FOR_DEMO_UNKNOWN_USP_NAME、certification_usp_codeをFOR_DEMO_UNKNOWN_USP_CODEとし、実在しない値と
一目で分かる形にする。本番DIPS2.0への接続時に項番20・21を格納する実装へ切り替える際、この固定値で
投入された行を検索して判別できるようにするためである(両カラムはNullableであり空のままにもできるが、
「未取得」と「収集できたが値が空」の区別を将来つけられるよう、固定値で埋める方針とする)。
なお両カラムを参照するAPIは現時点で存在しない(GeoSpatialの他Operator計画検索は認定USP情報を返さない)。 値の用途が生じるのは本番DIPS2.0接続以降である。
4.6. 飛行計画削除におけるDIPS取り下げの非対応
Section titled “4.6. 飛行計画削除におけるDIPS取り下げの非対応”飛行計画削除のうちDIPS通報済み(reportStatusがREPORTED)を前提とする取り下げ処理(5.5節 手順1-2)は、模擬DIPSに飛行計画の削除APIが存在せず取り下げ自体を実行できないため実装対象外とする(5.3節 手順1-7)。飛行計画通報(POST /api/v1/fp/flight-plans/{flightPlanId}/report)によりreportStatusがREPORTEDになった飛行計画も削除できるが、DIPS側の登録は残る。仕様そのものは将来のDIPS連携対応時にそのまま適用するため5.5節に記載する。
5. ユースケース毎の処理フロー
Section titled “5. ユースケース毎の処理フロー”5.1. UC-PLAN-01 飛行計画を作成する
Section titled “5.1. UC-PLAN-01 飛行計画を作成する”-
- 飛行計画仮登録
- 1-1. 飛行計画をデータベースに保存する。
- 飛行計画ステータス
FLIGHT_PLAN.statusはDRAFTとして登録する。 - 飛行終了日時は入力された飛行開始予定時刻
FlightPeriod.startTimeと所要時間FlightPeriod.plannedFlightTimeから算出する。仮登録時点の格納先はFLIGHT_PLAN_DRAFT.planned_end_atであり、FLIGHT_PLAN_REVISION.planned_end_atへ格納されるのは本登録以降である(DRAFTの間はFLIGHT_PLAN_REVISIONが存在しない。flight-plan-er.mdの「図1 カラム補足」)。 - パラメータとデータベースカラムの項目単位の対応関係はflight-plan-field-mapping.mdを参照。
- DIPS通報時にのみ使用される以下の項目はjsonb形式で
FLIGHT_PLAN_DIPS_ATTRS.dips_report_detailに集約して保存する。対象項目を以下に示す。
- 飛行計画ステータス
| 項目(日本語名) | OASリクエストフィールド | jsonbキー |
|---|---|---|
| 航続可能時間 | flightPeriod.plannedMaxTime | dips_report_detail.plannedMaxTime |
| 飛行速度 | flightSpec.speed | dips_report_detail.flightSpeed |
| 立入管理措置 | riskMitigation.types(ONSITE_CONTROL含むか) | dips_report_detail.riskMitigationOnsiteControl |
| 立入管理措置(レベル3飛行) | riskMitigation.types(ONSITE_CONTROL_LEVEL3含むか) | dips_report_detail.riskMitigationOnsiteControlL3 |
| 立入管理措置(レベル3.5飛行関連) | riskMitigation.types(ONSITE_CONTROL_LEVEL35含むか) | dips_report_detail.riskMitigationOnsiteControlL35 |
| 立入禁止措置 | riskMitigation.types(NO_ENTRY_CONTROL含むか) | dips_report_detail.riskMitigationOnsiteControl2 |
| 係留飛行 | riskMitigation.exceptionalConditionsMooring | dips_report_detail.exceptionalConditionsMooring |
| 補助者 | riskMitigation.assistantsNumber | dips_report_detail.assistantsNumber |
| その他特記事項 | otherInformation | dips_report_detail.otherInformation |
| 保険会社名 | insuranceInformation.insuranceCompany | dips_report_detail.insuranceInformation.insuranceCompany |
| 商品名 | insuranceInformation.insuranceProduct | dips_report_detail.insuranceInformation.insuranceProduct |
| 補償金額(対人) | insuranceInformation.interPerson | dips_report_detail.insuranceInformation.interPerson |
| 補償金額(対物) | insuranceInformation.interObject | dips_report_detail.insuranceInformation.interObject |
| 賠償能力 | insuranceInformation.insuranceAbility | dips_report_detail.insuranceInformation.insuranceAbility |
| 許可・承認番号 | flightPermitApplicationInfo.flightPermitApplicationNumber | dips_report_detail.flightPermitApplicationInfo.flightPermitApplicationNumber |
| 許可書発行日 | flightPermitApplicationInfo.permitDate | dips_report_detail.flightPermitApplicationInfo.permitDate |
| 許可期間(自) | flightPermitApplicationInfo.startDate | dips_report_detail.flightPermitApplicationInfo.startDate |
| 許可期間(至) | flightPermitApplicationInfo.finishDate | dips_report_detail.flightPermitApplicationInfo.finishDate |
| 連絡先氏名 | flightPermitApplicationInfo.contactPermit.name | dips_report_detail.flightPermitApplicationInfo.contactPermit.name |
| 連絡先国 | flightPermitApplicationInfo.contactPermit.country | dips_report_detail.flightPermitApplicationInfo.contactPermit.country |
| 連絡先都道府県 | flightPermitApplicationInfo.contactPermit.prefectures | dips_report_detail.flightPermitApplicationInfo.contactPermit.prefectures |
| 連絡先住所 | flightPermitApplicationInfo.contactPermit.address | dips_report_detail.flightPermitApplicationInfo.contactPermit.address |
| 連絡先電話番号(国コード) | flightPermitApplicationInfo.contactPermit.telephoneCountry | dips_report_detail.flightPermitApplicationInfo.contactPermit.telephoneCountry |
| 連絡先電話番号 | flightPermitApplicationInfo.contactPermit.telephone | dips_report_detail.flightPermitApplicationInfo.contactPermit.telephone |
| 連絡先メールアドレス | flightPermitApplicationInfo.contactPermit.email | dips_report_detail.flightPermitApplicationInfo.contactPermit.email |
-
1-2.
Idempotency-Keyヘッダーが指定されている場合はデータベースに保存する。- リトライ等による多重登録防止のため、
Idempotency-Keyヘッダーで同一キーの再送(ネットワークリトライ等)では重複作成せず、最初に作成された飛行計画を 201 で返す。同一キーを異なるリクエストボディで再利用した場合は 422 を返す。
- リトライ等による多重登録防止のため、
-
1-3. エラーケースに合わせてステータスコードを返却する。
- 400: リクエストボディのパース失敗(不正なJSON・型不一致など)
- 422: バリデーションエラー(DIPS必須項目の未入力等)、または
Idempotency-Keyを異なるリクエストボディで再利用した場合
-
- 飛行計画本登録
- 2-1. 入力項目の整合性チェックを行う。
- 2-1-1. 必須入力項目チェック
- 以下の項目が入力されていることを確認する。
- 飛行計画名称
name - 飛行目的
flightPurposes - 出発地
departurePoint - 飛行時間帯・所要時間
flightPeriod - 飛行速度・高度
flightSpec - 飛行経路
flyRoute - 目的地
destinationPoint - リスク軽減措置
riskMitigation - 操縦者情報(使用機体を含む)
pilotInfo - 調整連絡先メールアドレス
email
- 飛行計画名称
- 以下の項目が入力されていることを確認する。
- 2-1-2. 飛行時間帯の入力項目チェック
- 飛行開始予定日時が、JST(UTC+9)の暦日に変換した値でAPI実行日の前日以降、かつAPI実行日から1年以内に設定されていることを確認する(DIPSが登録・更新の両方で同じ判定を行うため。制約と出典はOASの
FlightPeriod.startTimeのdescription、対応関係はflight-plan-field-mapping.mdの「DIPS側の入力チェックとAPI制約の対応」を参照)。判定は時刻単位ではなく暦日単位で行う。 - 飛行終了予定日時(
flightPeriod.startTimeとplannedFlightTimeから算出)が飛行開始予定日時より後であることを確認する(FLIGHT_PLAN_REVISIONのリビジョン内容が持つ不変条件。DB制約では表現していないため確認はバックエンドで行う)。
- 飛行開始予定日時が、JST(UTC+9)の暦日に変換した値でAPI実行日の前日以降、かつAPI実行日から1年以内に設定されていることを確認する(DIPSが登録・更新の両方で同じ判定を行うため。制約と出典はOASの
- 2-1-3. 飛行目的の入力項目チェック
- 飛行目的
flightPurposes[].codeがOTHER_BUSINESS(その他1・業務)またはOTHER_NON_BUSINESS(その他2・業務以外)の場合は、該当する行の補足説明flightPurposes[].noteが入力されていることを確認する。
- 飛行目的
- 2-1-4. 飛行経路の入力項目チェック
- 飛行経路種別
flyRoute.typeがroute(UTM内部のROUTE)の場合、バッファ幅flyRoute.bufferMが0より大きく100以下であることを確認する(FLIGHT_PLAN_AREA_ROUTE.buffer_mの制約に対応)。
- 飛行経路種別
- 2-1-5. コード値の入力項目チェック
- 飛行空域
flightAirspace・飛行方法flightType・リスク軽減措置riskMitigation.typesの各要素が、OASで定めるコードの値域(FlightAirspaceCode・FlightTypeCode・RiskMitigationType)内であることを確認する。riskMitigation.typesはFLIGHT_PLAN_DIPS_ATTRS.dips_report_detail(jsonb)へそのまま格納する項目だが、値域外の値を格納すると飛行計画詳細取得(5.2節 2-1)で値を復元できないため、他のコード項目と同じく本登録時に確認する。
- 飛行空域
- 2-1-6. 飛行経路の幾何的な妥当性チェック
- 飛行経路種別
flyRoute.typeがpolygonの場合、多角形が幾何的に妥当であることを確認する(自己交差する多角形はOASのGeoJsonPolygonのdescriptionが定めるとおり無効なリクエストとして扱い、自動補正は行わない)。頂点数・座標範囲はGeoJSONの構造として宣言できる制約のため本チェックの対象ではなく、構造だけでは表現できない幾何的な妥当性を本チェックで確認する。 - 対象は
polygonのみとする。circleは中心点と半径、route(UTM内部のROUTE)は経路とバッファ幅で表され、頂点数と座標範囲の制約を満たす限り自己交差を取り得ないためである。
- 飛行経路種別
- 2-1-7. 高度換算(AGL→AMSL・WGS84楕円体高)の可否チェック
- Elevation Serviceが有効(
elevation.enabled=true)な環境でのみ本チェックを行う。飛行経路flyRouteが表す形状(FLIGHT_PLAN_AREA.plan_geometry・operational_intent_geometry・top_bottom_3d_geometryに相当)の全頂点について、AltitudeConverter(domain.port.AltitudeConverter)でAGLからAMSL・WGS84楕円体高への変換を試みる。 - 1頂点でも地表標高データ(DEM)またはジオイド高データが当該地点をカバーしておらず変換できない場合(
Optional.empty())、無効なリクエストとして扱う。遅延充填は行わない。elevation.enabled=trueの環境でDBに保存する時点では、AGL・AMSL・WGS84楕円体高の換算値(スカラーのmin/max_altitude_m_amsl・min/max_altitude_m_wgs84、および形状の_wgs84列)が全て揃っていることを保証する(設計根拠はflight-plan-er.mdの「高度は換算値をカラムに保持する」・elevation-service-design.md7.4節「一部頂点だけDEM未整備だった場合の扱い」)。 - 変換に成功した場合、換算結果を
FLIGHT_PLAN_AREAの対応するカラムへ保存する(2-3で本登録の一部として書き込む)。 elevation.enabled=false(既定値。ローカル開発等でDEM・ジオイドファイルを用意していない環境)では本チェックを行わず、上記カラムはNULLのまま保存する(拒否しない)。DEM・ジオイドファイル未配置でも飛行計画機能自体の開発・動作確認を妨げないためで、elevation.enabledのデフォルトをfalseにした方針(ElevationPropertiesのjavadoc)に合わせる。
- Elevation Serviceが有効(
- 2-1-1. 必須入力項目チェック
- 2-2. 空域制限との競合判定を行う。
- 2-2-1. 判定の位置づけ
- 競合が検出されても本登録は成功する。 飛行計画は
ACCEPTEDへ遷移し、検出結果をconflict.airspaceRestrictionsに設定して200を返す。競合を理由に4xxを返すことはしない(OASのregisterFlightPlanのdescriptionが「飛行計画エリアについて空域制限との競合有無を確認し、競合が発生していればconflictに競合対象の空域制限IDを設定して返却するとともに、Operator宛にエリア競合発生のメール通知を行う(この場合も本登録自体は成功する)。」と定める。docs/openapi/frontend/flight-planning.yamlのregisterFlightPlan)。 - 判定は2-1の入力項目の整合性チェックを通した内容に対して行う。検出結果は2-3で作成する初版リビジョンに紐づけて記録するため、リビジョンの作成・ステータス更新と同一トランザクションで行う。
- 競合が検出されても本登録は成功する。 飛行計画は
- 2-2-2. 判定対象の空域制限の抽出
- 空域制限ドメインの
AIRSPACE_RESTRICTION(別スキーマ)から、次をすべて満たす行を判定対象とする。- 取得元での存在状態
statusがACTIVEであること(DISAPPEAREDは取得元の取得結果から消えた行のため対象外とする) - 有効期間
valid_from〜valid_toが飛行時間帯(FLIGHT_PLAN_REVISION.planned_start_at〜planned_end_at)と重なること。判定式はvalid_from <= planned_end_at AND valid_to >= planned_start_atとし、GeoSpatialの空域制限検索がvalidFrom/validToに用いる区間重なり判定に揃える(geospatial-api-design.mdの6.3節「区間重なり判定:valid_from <= validTo AND valid_to >= validFrom」) - 水平形状
geometryが飛行領域と重なること(2-2-3)
- 取得元での存在状態
- 組織による絞り込みは行わない。空域制限は全組織が同一のデータを参照し、
AIRSPACE_RESTRICTIONはorganization_idを持たない(airspace-er.mdの「組織スコープを持たない」)。 - 制限分類
categoryによる絞り込みも行わない。ただしDENSELY_INHABITED_DISTRICT(人口集中地区)は10月デモの間AIRSPACE_RESTRICTIONに行を持たないため(airspace-er.mdの決定事項D2。デモではGSI由来の静的PMTilesとしてのみ配信する)、デモ期間はこの種別との競合が検出されない。これは配信方式に由来するデモ期間限定の制限であり、判定の対象から外す決定ではない。デモ以降はDIDも行として保持し、他の分類と同じく判定の対象になる。
- 空域制限ドメインの
- 2-2-3. 飛行領域との重なりの判定
- 判定は飛行領域のジオメトリのみを用いた2次元(水平方向)判定とし、高度は使用しない(OASの
FlyRouteInputのdescription「空域制限・他の飛行計画との競合判定は本ジオメトリのみを用いた2次元(水平方向)判定とする」docs/openapi/frontend/flight-planning.yamlのFlyRouteInput)。空域制限側が高度範囲(min_altitude_agl/max_altitude_agl・top_bottom_3d_geometry)を持つ場合も判定には用いない。 - 飛行計画側は表示用に多角形化した
plan_geometryを用いない。近似誤差を含むためである(flight-plan-er.mdの「geometry(元形状)も残す理由」)。飛行領域の形状area_typeごとの判定は次のとおり。POLYGON: 多角形FLIGHT_PLAN_AREA.geometryが空域制限のgeometryと交差するかCIRCLE: 中心点FLIGHT_PLAN_AREA.geometryと半径FLIGHT_PLAN_AREA_CIRCLE.radius_mが表す円が空域制限のgeometryと交差するかROUTE: バッファ幅FLIGHT_PLAN_AREA_ROUTE.buffer_mを適用済みのFLIGHT_PLAN_AREA.operational_intent_geometryが空域制限のgeometryと交差するか
ROUTEは経路からの距離ではなく、DIPSへ通報するのと同じ形状で判定する。 経路のgeometry(LineString)からbuffer_m以内という距離判定は丸いバッファになるが、operational_intent_geometryはquad_segs=1 endcap=square join=mitreの角形バッファで、経路端では角がbuffer_m×√2まで外側に出る。距離判定にするとDIPSへ申告済みの領域の中にある空域制限を見落とすため、両者を揃える(flight-plan-er.mdの「同じパラメータをoperational_intent_geometryの生成にも適用する」が「通報用と内部判定用でパラメータが違うと、DIPSへ申告した形状とUTM内部で競合判定する形状が別物になる」と定める)。CIRCLEの交差は、円を多角形化せず中心点と半径のままの距離で評価する。 中心から空域制限のgeometryまでの距離がradius_m以下であることは、その円がgeometryと交差することと同値であり、表示用に多角形化したplan_geometryの近似誤差を判定に持ち込まずに済む。距離はメートル単位で判定するためgeographyへキャストして評価する(flight-plan-er.mdの「メートル単位の空間演算はgeographyへキャストして行う」)。この条件はgeometryに張ったGiSTインデックスでは使えないため、空域制限側にgeometry::geographyの式インデックスを持つ(飛行計画側は対象リビジョンの1行に絞り込んでから空域制限側を探索するため、同じ式の索引は持たない)。ROUTE・POLYGONの交差判定はgeometryのGiSTインデックスを使う。- 境界が接するだけの場合も重なりとして扱う。 飛行が制限の境界線上にかかる状態をOperatorに知らせないより、検出して警告する側に倒す(検出は本登録を妨げないため、過検出の不利益が小さい)。
- 判定は飛行領域のジオメトリのみを用いた2次元(水平方向)判定とし、高度は使用しない(OASの
- 2-2-4. 検出結果を
CONFLICT_DETECTIONへ記録する- 競合した空域制限1件につき1行を追加する。各カラムに設定する値は次のとおり。
flight_plan_id: 対象の飛行計画detected_against_revision_id: 2-3で作成する初版リビジョンairspace_restriction_id: 競合したAIRSPACE_RESTRICTION.id(UTMが採番したuuid)airspace_restriction_type: 検出時点のAIRSPACE_RESTRICTION.category(スナップショット)status:DETECTEDdetected_at: 判定を行った時刻detection_params: 判定に用いた条件(2-2-3の判定方式と、方式ごとの条件。CIRCLEは円の半径radius_m、ROUTEは交差判定に用いた領域のバッファ幅buffer_m、POLYGONは条件なし)
airspace_restriction_idはスキーマが異なるためFK制約を持たない。参照先の存在はアプリケーション側で保証する(flight-plan-er.mdの「空域制限との競合は判定結果を保持し、未チェックとの区別は状態から導出する」)。airspace_restriction_typeを検出時点のスナップショットとして持つのは、検出履歴がイミュータブルであるべきであり、空域制限側の種別が後から変わっても検出時点の値を保つためである(同上)。したがって応答の組み立てで空域制限ドメインを引き直さない。statusはDETECTEDのみを設定する。10月デモでは検出のみを扱い、RESOLVING・RESOLVED・IGNOREDへ遷移させるAPIを持たない(flight-plan-er.mdの「空域制限との競合の状態」)。- 競合が0件の場合は行を追加しない。 「未チェック」と「チェック済みで競合なし」は
FLIGHT_PLAN.statusから導出する(DRAFTは未チェック、ACCEPTED以降はチェック済み)。この区別のためのカラム・行は持たない。 - 既存行の書き換え・削除は行わない(
CONFLICT_DETECTIONはイミュータブル)。再判定は「飛行計画更新」(5.2 手順3-2)でリビジョン単位に行い、新しいリビジョンに紐づく行を追加する。
- 競合した空域制限1件につき1行を追加する。各カラムに設定する値は次のとおり。
- 2-2-5. 検出結果を応答に設定する
- 2-2-4で追加した行から
conflict.airspaceRestrictionsを組み立てる。airspaceRestrictionIdにはairspace_restriction_idをそのまま設定する(OASのAirspaceRestrictionConflict.airspaceRestrictionIdはUTMが採番したuuidを返す契約であるため、空域制限ドメインを引いて外部IDへ解決する処理は不要)。
- 2-2-4で追加した行から
- 2-2-6. 競合検出時のOperatorへの通知
- forDemo向けでは対象外機能のため実装未対応とする。
- 2-2-1. 判定の位置づけ
- 2-3. 飛行計画仮登録でデータベースに保存された飛行計画のステータスを
ACCEPTEDに更新する。DRAFT以外の飛行計画ステータスを対象に指定された場合は、409エラーを返却する。
- 2-4.
Idempotency-Keyヘッダーが指定されている場合はデータベースに保存する。- リトライ等による多重登録防止のため、
Idempotency-Keyヘッダーで同一キーの再送(ネットワークリトライ等)では重複作成せず、最初に作成された飛行計画を 201 で返す。同一キーを異なるリクエストボディで再利用した場合は 422 を返す。
- リトライ等による多重登録防止のため、
- 2-5. エラーケースに合わせてステータスコードを返却する。
- 400: リクエストボディまたはパスパラメータ(
flightPlanId)のパース失敗 - 404: 指定された飛行計画IDの飛行計画が存在しない
- 409: 対象の飛行計画が
DRAFT以外の状態(状態遷移違反)、または他の処理によるロック中でアクセス不可(typeで区別) - 422: 2-1の入力項目の整合性チェック(必須入力項目・値域・条件付き必須・飛行経路の幾何的な妥当性・高度換算の可否)違反によるバリデーションエラー、または
Idempotency-Keyを異なる飛行計画IDに対して再利用した場合
- 400: リクエストボディまたはパスパラメータ(
5.2. UC-PLAN-03 飛行計画を確認・更新する
Section titled “5.2. UC-PLAN-03 飛行計画を確認・更新する”-
- 飛行計画一覧取得
- 1-1. 飛行計画情報の一覧をデータベースから取得する。
- 1-1-1. 検索条件
- 以下の内容をAND条件で絞り込んだ飛行計画情報をレスポンスに設定する。
- ログインユーザが所属している組織に属するユーザから登録されていること(本条件はIAMServiceが設定したRLSにより透過的にフィルタされる)
- 削除されていないこと(
FLIGHT_PLAN.deleted_atが未設定であること。飛行計画削除は論理削除のため行自体は残るが、削除済みの飛行計画は本APIの対象としない。5.5節 手順1-3参照) - クエリパラメータに指定された登録者IDにて登録されていること(未指定の場合は条件なし)
- クエリパラメータに指定された時刻範囲で計画されていること(時刻範囲に一部でも該当している場合は対象とする)(未指定の場合は条件なし)
- 以下の内容をAND条件で絞り込んだ飛行計画情報をレスポンスに設定する。
- 1-1-2. ページング機能
- forDemoでは設計対象外とする。
- 1-1-3. jsonbに格納された項目の取り扱い
- 飛行計画詳細取得(2-1)と同じ。
- 1-1-4. 空域制限との競合の取り扱い
- 飛行計画詳細取得(2-1)と同じ。
- 1-1-1. 検索条件
- 1-2. エラーケースに合わせてステータスコードを返却する。
- 400: クエリパラメータ(
periodFrom/periodTo)の形式が不正
- 400: クエリパラメータ(
-
- 飛行計画詳細取得
- 2-1. 飛行計画情報をデータベースから取得する。
- 指定された飛行計画IDの飛行計画情報をデータベースから取得する。
- 削除されていない(
FLIGHT_PLAN.deleted_atが未設定の)飛行計画のみを対象とする。飛行計画削除は論理削除のため行自体は残るが、削除済みの飛行計画IDを指定された場合は存在しないものとして扱い404を返却する(5.5節 手順1-3参照)。 - パラメータとデータベースカラムの項目単位の対応関係はflight-plan-field-mapping.mdを参照。
- 一時保存内容(
FLIGHT_PLAN_DRAFT.draft_fields)とDIPS通報時にのみ使用する項目(FLIGHT_PLAN_DIPS_ATTRS.dips_report_detail)はjsonbのため、格納されている値がAPIの型・コードの値域に適合しない場合がありうる。適合しない項目は未入力と同様にnullまたはキー省略で返し、エラーとはしない(1項目の不適合により飛行計画そのものが取得できなくなることを避ける)。- 型・値域には適合するが、OASが
requiredと宣言した項目を欠くオブジェクト(例:permitDateのないflightPermitApplicationInfo)も同じ扱いとする。欠けたまま返すとOASに適合しない応答になり、OASから生成した型を使うクライアントでは応答全体の復元に失敗しうるためである。
- 型・値域には適合するが、OASが
- 空域制限との競合
conflict.airspaceRestrictionsは再判定せず、現在リビジョンFLIGHT_PLAN.current_revision_idに紐づくCONFLICT_DETECTIONの行から組み立てる(直近の「飛行計画本登録」(5.1 手順2-2)・「飛行計画更新」(3-2)時点の判定結果)。DRAFTの間はリビジョンが存在せず行も無いため空配列になる。- 空配列は「保持している競合が0件」を意味し、チェック済みかどうかは表さない。チェックの有無は
statusから判断する(DRAFTは未チェック、ACCEPTED以降はチェック済み)。
- 空配列は「保持している競合が0件」を意味し、チェック済みかどうかは表さない。チェックの有無は
- 2-2. エラーケースに合わせてステータスコードを返却する。
- 400: パスパラメータ(
flightPlanId)の形式が不正 - 404: 指定された飛行計画IDの飛行計画が存在しない(削除済みの飛行計画IDを指定された場合を含む)
- 400: パスパラメータ(
-
- 飛行計画更新
- 3-1. リクエストボディの内容でデータベースの飛行計画情報を更新する。
- 部分更新(JSON Merge Patch方式)とする。
name以外の各項目はキーを省略すると既存の値を変更せず、明示的にnullを指定すると入力済みの内容を未入力の状態に戻す(一度入力した項目を未入力に戻す更新を可能にするため)。値を指定すればその値に更新する。nameは常に必須のためnullを指定できない。 - パラメータとデータベースカラムの項目単位の対応関係はflight-plan-field-mapping.mdを参照。
- 部分更新(JSON Merge Patch方式)とする。
- 3-2. 対象の飛行計画ステータス
FLIGHT_PLAN.statusに応じて検証の厳しさを変える。DRAFTの間: 項目間の整合性検証・空域制限との競合判定は行わない(入力途中の内容のまま保存できる)。ACCEPTED以降: 「飛行計画本登録」(5.1 手順2-1)と同じ入力項目の整合性チェック(2-1-1〜2-1-7のすべて)および空域制限との競合判定を、パッチ適用後の内容に対して適用する。本登録で弾かれる内容が更新経由で保存できてはならないため、一部だけ緩めることはしない。- 飛行経路の幾何的な妥当性チェック(2-1-6)・高度換算の可否チェック(2-1-7)は、当該項目を変更しない更新でも適用する(飛行経路を変更しない更新でも
FLIGHT_PLAN_AREAの換算値カラムは新しいリビジョンの行として作り直されるため)。 - 空域制限との競合判定は「飛行計画本登録」(5.1 手順2-2)と同じ条件で行う。判定対象の抽出(2-2-2)・飛行領域との重なりの判定(2-2-3)・応答への設定(2-2-5)・Operatorへの通知(2-2-6)はいずれも本登録と同じであり、次の2点だけが異なる。
- 検出結果(2-2-4)は本更新で作成する次版リビジョンに紐づけて追加する。以前のリビジョンに紐づく行は書き換えず、削除もしない(
CONFLICT_DETECTIONはイミュータブル)。したがって判定結果はリビジョン単位で置き換わり、応答・取得系が返すのは現在リビジョンに紐づく行だけになる。 - 飛行経路・飛行時間帯を変更しない更新でも判定をやり直す。空域制限側のデータが前回の判定後に変わっている場合があり、飛行計画側の変更の有無では判定結果が変わらないことを保証できないためである。
- 検出結果(2-2-4)は本更新で作成する次版リビジョンに紐づけて追加する。以前のリビジョンに紐づく行は書き換えず、削除もしない(
- 次版リビジョンを作らない場合(内容が現在リビジョンと同一の場合。後述)は判定も記録も行わない。 現在リビジョンに紐づく既存の判定結果がそのまま有効であり続けるため、応答にもその内容を設定する。
- 競合が検出されても更新は成功する(200)。競合を理由に4xxを返すことはしない(本登録と同じ扱い)。
- パッチ適用後の内容が現在リビジョンの内容と同一の場合は、次版リビジョンを作らず何も書き込まない(
FLIGHT_PLAN.updated_atも進めない)。空のリクエストボディ({})による再送や、現在値と同じ値での上書きでリビジョンが増え、そのたびに3-3により通報済みの状態が失われて再通報が必要になることを避けるためである。この場合も200を返し、応答には現在リビジョンの値(reportStatusは通報済みならREPORTEDのまま)を設定する。- 「同一」は保存される内容の値で判定する。比較対象は
FLIGHT_PLAN_REVISION・FLIGHT_PLAN_DIPS_ATTRS・FLIGHT_PLAN_AREA(+サブタイプ)・FLIGHT_PLAN_DIPS_PURPOSE・FLIGHT_PLAN_PILOT_ASSIGNMENTが保持する内容の項目であり、リビジョン自身の識別・履歴の項目(id・revision_no・parent_revision_id・change_type・change_reason・changed_by・changed_at)とreportStatusは含めない。飛行開始・終了予定日時はtimestamptzがオフセットを保持しないため同一時点かどうかで判定する(2026-10-01T10:00+09:00と2026-10-01T01:00Zは同一とみなす)。 - 同一判定は3-2の検証を通した後に行う。内容が変わらない場合でもパッチが不正なら422・400で弾く(検証を緩めない)。リクエストボディにキーが含まれるかどうかでは判定しないため、同じ値を明示的に指定した更新も「変更なし」となる。
- 「同一」は保存される内容の値で判定する。比較対象は
- 飛行経路の幾何的な妥当性チェック(2-1-6)・高度換算の可否チェック(2-1-7)は、当該項目を変更しない更新でも適用する(飛行経路を変更しない更新でも
- 本APIによる更新で
statusがDRAFTに戻ることはない(ACCEPTED以降の飛行計画はACCEPTED以降のまま維持される)。 DRAFTの間は内容が同一でもFLIGHT_PLAN_DRAFT行を上書きする(ACCEPTED以降の同一判定を適用しない)。上の判定が避けているのはリビジョンの増加と通報済み状態の喪失であり、DRAFTは単一の可変行でリビジョンを持たずreportStatusも未通報固定のため、いずれも起こらない。
- 3-3. 対象がDIPS通報済み(
reportStatusがREPORTED)で、かつ内容が変わった場合、更新後の内容の再通報が必要になるためreportStatusをUNREPORTEDに戻す(内容が変わらない場合は3-2によりREPORTEDのまま維持される)。- これに伴い
conflict.flightPlanIds(DIPS通報で判明する、他の飛行計画との競合)も空配列に戻る(再通報するまで再判定されない)。 dipsFlightPlanIdは初回通報時の値を保持したままとなる。再通報はDIPS APIをmode=1(更新)で呼び出すため、受付番号としてdipsFlightPlanIdの送信が必須であるためで(flight-plan-field-mapping.md参照)、再通報自体は「飛行計画通報」(POST /flight-plans/{flightPlanId}/report)を再度呼び出して行う。
- これに伴い
- 3-4. エラーケースに合わせてステータスコードを返却する。
- 400: リクエストボディまたはパスパラメータ(
flightPlanId)のパース失敗 - 404: 指定された飛行計画IDの飛行計画が存在しない
- 409: 対象の飛行計画が
CANCELLED・ENDEDの状態(状態遷移違反)、または他の処理によるロック中でアクセス不可(typeで区別) - 422: 3-2の入力項目の整合性チェック(必須入力項目・値域・条件付き必須・飛行経路の幾何的な妥当性・高度換算の可否)違反によるバリデーションエラー、または
nameに明示的なnullを指定した場合(3-1)
- 400: リクエストボディまたはパスパラメータ(
5.3. UC-DIPS-03 飛行計画を通報する
Section titled “5.3. UC-DIPS-03 飛行計画を通報する”飛行計画通報(POST /api/v1/fp/flight-plans/{flightPlanId}/report)は、DIPS(forDemoでは模擬DIPS)へ飛行計画を通報し、DIPS通報状態FLIGHT_PLAN_STATE_EVENT.report_status_afterを更新する。運航状態FLIGHT_PLAN.statusは本APIでは変化しない。
外部呼び出しを伴う状態遷移のため、ADR-022に従い「中間状態への遷移 → トランザクション外でのDIPS呼び出し → 応答による確定」の順で行い、DIPS呼び出しの間は対象行のロックを保持しない。状態の遷移先はflight-plan-statemachine.mdの「DIPS通報状態(report_status)の遷移」を正とする。
-
- 飛行計画通報
- 1-1. 通報の事前条件を確認する。
- 飛行計画ステータス
FLIGHT_PLAN.statusがACCEPTEDであること。本登録済みの飛行計画のみ通報できる。 - DIPS通報状態が
UNREPORTEDまたはREPORTEDであること。通報実行中(REPORTING)の再操作(画面の二度押し等)と、取り下げ済み(WITHDRAWING・WITHDRAWN)を弾く。通報済み(REPORTED)からの再通報は許す。内容が変わっていなくてもDIPS側の通報時刻が最新化され、UNREPORTEDへ戻す遷移が漏れた場合のフェールセーフにもなる。再通報は受付番号を送る更新扱いになるため二重登録にはならない(手順1-3)。 - DIPS通報義務
FLIGHT_PLAN_DIPS_ATTRS.report_requiredは事前条件にしない。同カラムは通報が必須かどうかを表すものであり通報の可否を決めない。DIPSは義務のない飛行の通報も受理するため、falseの飛行計画からの通報も受け付ける。
- 飛行計画ステータス
- 1-2. DIPS通報状態を
REPORTINGに更新する。- DIPS呼び出しの前に更新し、
FLIGHT_PLAN_STATE_EVENTにREPORT_STARTを追記する。 - 通報では飛行計画の内容が変わらないため、新しいリビジョンは作成しない。追記するイベント行は、対象リビジョン(
flight_plan_revision_id=通報時点の現在リビジョン)・遷移後の運航状態(status_after=直前の値をそのまま引き継ぐ)・実行者(actor_user_id)・発生日時(occurred_at)を持つ。 - 状態遷移も最終更新に含まれるため、導出キャッシュである
FLIGHT_PLAN.updated_atをイベントの発生日時に追随させる。
- DIPS呼び出しの前に更新し、
- 1-3. DIPSへ通報する。
- 送信内容は現行リビジョン(
FLIGHT_PLAN.current_revision_id)の内容から組み立てる。項目単位の対応関係はflight-plan-field-mapping.mdを参照。 - 一度も通報していない飛行計画は飛行計画IDを空で送信し、DIPS側で新規登録となる。通報済みの飛行計画を再通報する場合(内容を更新して
UNREPORTEDへ戻った場合と、REPORTEDのまま再通報する場合の両方)はDIPS_REPORT.dips_receipt_no(受付番号)を送信し、DIPS側で更新となる(模擬DIPSは登録・更新を同一APIで受け付ける)。
- 送信内容は現行リビジョン(
- 1-4. 応答に応じてDIPS通報状態を確定する。
- 通報成功: DIPS通報状態を
REPORTEDに更新し、REPORT_COMPLETEを追記する。あわせてDIPS_REPORTに1行追加し、応答の飛行計画ID(受付番号)をdips_receipt_noに保存する。行は通報したリビジョン(flight_plan_revision_id)に紐づけ、結果(status)は通報完了、通報日時(reported_at)と最終同期日時(last_synced_at)にはこの通報の時刻を設定する。他の飛行計画との重複が通知された場合も通報は成功として扱う。 - DIPS呼び出しの間は対象の飛行計画をロックしないため、確定の直前に対象を取り直す。取り直した時点でDIPS通報状態が
REPORTINGでない場合(他の操作が割り込んだ場合)は確定させず、状態不正として扱う。 - 通報失敗(DIPSが要求を受理しなかった業務エラー、DIPSへ到達できない、応答を解釈できない): DIPS通報状態を通報を始める前の状態(
UNREPORTEDからの通報ならUNREPORTED、再通報ならREPORTED)に戻し、REPORT_FAILを追記する。Operatorは内容を修正して再度通報できる。再通報の失敗でUNREPORTEDにしないのは、DIPS側に前回の通報が残っており、実態と食い違うためである。DIPSが受理しなかった場合は、到達できなかった場合と同じ503で返すがtypeで区別する(/problems/dips-report-rejected)。 - 成否不明(リクエストは届いたが結果が分からない): DIPS通報状態は
REPORTINGのまま保持し、REPORT_TIMEOUTを追記する。UNREPORTEDに戻すと再通報により二重登録となるおそれがあるため戻さない(滞留した飛行計画の状態確認手段は未整備。ADR-022の残課題)。
- 通報成功: DIPS通報状態を
- 1-5. 通報結果をレスポンスに設定する。
- DIPS通報状態
reportStatusと、初回の通報成功時に保存した受付番号dipsFlightPlanIdを返す。受付番号は計画内で不変であり、再通報しても変わらない。 - DIPSの応答に他の飛行計画との重複が含まれる場合、その飛行計画IDを
conflict.flightPlanIdsに設定する。この値はデータベースに保存しない。 飛行計画一覧取得・詳細取得が返す飛行計画同士の競合は運航調整ドメイン(coordination.confliction)から算出するため、通報時の応答を保持する必要がない。 - 空域制限との競合
conflict.airspaceRestrictionsは本APIでは再判定せず、現在リビジョンに紐づくCONFLICT_DETECTIONの行をそのまま返す(飛行計画一覧取得・飛行計画詳細取得と同じ組み立て。5.2 手順2-1)。通報は新しいリビジョンを作らないため(手順1-2)、直近の本登録・更新時点の判定結果はそのまま有効である。
- DIPS通報状態
- 1-6. エラーケースに合わせてステータスコードを返却する。
- 400: パスパラメータ(
flightPlanId)の形式が不正 - 404: 指定された飛行計画IDの飛行計画が存在しない
- 409:
statusがACCEPTED以外、またはDIPS通報状態がUNREPORTED・REPORTEDのいずれでもない(1-1の事前条件を満たさない状態遷移違反) - 502: DIPSから完全な応答を受け取ったが解釈できなかった
- 503: DIPSへ到達できなかった(
/problems/dips-call-failed)、またはDIPSが通報を受理しなかった(/problems/dips-report-rejected) - 504: DIPSへリクエストは届いたが結果が分からなかった
- 400: パスパラメータ(
- 1-7. forDemoでの制限
Idempotency-Keyヘッダーによる重複排除は他のAPIと同様に未対応とする(ADR-020)。同一キーでの再送はその都度DIPSへの通報として扱われる。- DIPSへの通報はIX-UTM共通のクライアント証明書で行うため、DIPS上の通報者はすべてのOperatorで同一になる。
- DIPS通報の取り下げ(
WITHDRAWING/WITHDRAWNへの遷移)は対応しない。模擬DIPSに飛行計画の削除APIが存在しないため。
5.4. UC-PLAN-01-10 飛行計画付近の他の飛行計画を収集する
Section titled “5.4. UC-PLAN-01-10 飛行計画付近の他の飛行計画を収集する”本ユースケースはUC-PLAN-01の一部だが、Operatorの明示的な操作を契機として飛行計画の確認・更新の流れ
(5.2)の中から実行されるため、独立した節として記載する(flight-plan-sequence.mdの
「2. 確認(Read)」のopt 飛行計画周辺データの最新化を参照)。定期バッチではなく、対象の飛行計画1件を
起点としたオンデマンドの収集である。
収集したデータは地図への重畳表示に用いるため、本APIの応答には含めない。閲覧はGeoSpatialのエリア範囲 指定API(他Operator計画検索)が収集結果を参照して行う(本APIの対象外)。
-
- 飛行計画周辺データ更新
- 1-1. 対象の飛行計画を取得し、収集の可否を判定する。
- 指定された飛行計画IDの飛行計画を取得する。存在しない場合・論理削除済みの場合は404を返す(他組織の 飛行計画はIAMServiceが設定したRLSによりフィルタされるため、同じく404となる)。
- 収集の対象とするのは、飛行計画ステータス
FLIGHT_PLAN.statusがACCEPTED(本登録済み)またはACTIVATED(飛行中)の場合のみとする。DRAFT・ENDED・CANCELLEDを対象に指定された場合は 409を返す(本登録の状態遷移違反(5.1 2-3)と同じ扱いとする)。除外の理由は状態ごとに異なる。DRAFT: 収集結果を紐づける起点リビジョンが存在しないため。DRAFTの間はFLIGHT_PLAN.current_revision_idがNULLであり(flight-plan-er.mdの 「図1 カラム補足」)、リンクはリビジョン単位で作成するため保存できない。forDemoでは仮登録時に DIPS必須項目をすべて入力させる(4.1)が、飛行計画ステータスは本登録までDRAFTのままであるため、 周辺データ更新は本登録後にのみ実行できる。ENDED・CANCELLED: 収集結果は「いま周辺に何がいるか」を地図に示すための現況データであり (flight-plan-er.mdの 「保持するのは最新リビジョンの収集結果のみ」)、飛行を終えた計画・取り下げた計画について周辺状況を 最新化する必要がないため。両状態はリビジョンを持つため保存自体は可能であり、除外は業務上の判断による。
ENDED・CANCELLEDへ遷移した時点で、その計画に紐づく既存のリンク(FLIGHT_PLAN_DIPS_NEARBY_LINK)は 削除する。更新を止める一方でリンクを残すと、最新化されない収集結果がGeoSpatialの他Operator計画検索に 現れ続けるためである(同検索は収集起点の計画の状態では絞り込まない)。飛行終了・飛行取消のAPIは本節の 対象外のため、削除の実装はそれぞれのAPIを実装するPRで対応する。- 飛行計画更新によりリビジョンが新しくなった場合は、飛行領域(
flyRoute)と飛行予定時刻 (planned_start_at/planned_end_at)のいずれかが変わったときだけリンクを削除する。 どちらも 変わらない場合は削除せず、リンクのflight_plan_revision_idを新しいリビジョンへ張り替える (collected_atは収集時刻の事実であるため更新しない)。「周辺」の範囲は1-2のとおりこの2つからのみ 算出されるため、他の項目(名称・操縦者・DIPS通報項目など)だけを変える更新では収集結果は有効であり、 これを捨てるとOperatorは無関係な編集のたびに収集をやり直すことになる。削除ではなく張り替えとするのは、 リンクを旧リビジョンに残す形にするとflight-plan-er.mdの 「保持するのは最新リビジョンの収集結果のみ」が成立しなくなり、flight_plan_revision_idの意味が 「最新リビジョン」から「収集時点のリビジョン」へ弱まるためである。- 変更の判定は、リビジョンが持つ
flyRoute・飛行予定時刻の値そのものを比較して行う。1-2で算出する 検索条件(100m広げた形状・JSTの暦日)を比較対象にしない。検索時間帯は暦日全体に広げるため同一暦日内の 時刻変更では検索条件が変わらず、最高対地高度は検索条件に影響しないが、これらを織り込むと 判定が1-2の変換規則に依存し、規則を狭める変更があったときに判定が黙って誤る。値そのものの比較は 過剰に削除する方向(高度のみの変更、閉環表現・頂点の並びの違い)に外れるだけで、Operatorが 収集をやり直せば済む。取りこぼす方向の誤りは、存在しない周辺計画を地図に出し続ける。 - 飛行計画更新API(
updateFlightPlan)は実装済みだが、この判定と張り替えは入っていない (DipsFlightPlanRepositoryに張り替え用のメソッドも無い)。削除時の連動削除(5.5節 手順1-3)と あわせてfollowups.mdの台帳に登録し、別PRで実装する。
- 変更の判定は、リビジョンが持つ
- 1-2. 現行リビジョンの内容からDIPS参照APIの検索条件を算出する。
- 検索範囲・検索時間帯は、
FLIGHT_PLAN.current_revision_idが指すリビジョンの飛行領域(flyRoute)と 飛行時間帯(FLIGHT_PLAN_REVISION.planned_start_at/planned_end_at)から算出する。対象の飛行計画が すでに登録している内容だけを用い、リクエストボディでの条件指定は受け付けない。 - 検索時間帯は飛行時間帯そのものではなく、飛行日の0時から24時までの暦日全体とする。飛行時間帯に
重なる他の飛行計画を漏れなく得ることが目的であり、暦日単位に広げておけば分単位の丸めや境界の判定が
不要になるためである。暦日はJST(UTC+9)で判定する。飛行日がJSTの暦日として運用される概念だから
であり、DIPS側のタイムゾーンが何であるかとは無関係である。 どの24時間を検索するかと、その時刻を
ワイヤ上でどのタイムゾーンで表記するかは別の関心事であり、後者は
DipsApiClientの実装が同じ時点を DIPS側のタイムゾーンへ変換して送る(模擬DIPSはUTCのため、JSTの0時〜24時はyyyyMMdd□HHmmでは 前日15時〜当日15時として送られる。時点が変わらないため飛行時間帯を包含し、検索時間帯の上限 24時間も満たす)。下限の判定だけはDIPS側のタイムゾーンの暦日に従う(後述)。 - 飛行開始日時と飛行終了日時のJST暦日が異なる(日付をまたぐ)場合は、翌日の0時から24時までも 検索対象とし、DIPS参照APIを暦日ごとに1回ずつ、計2回呼び出す。DIPSは検索開始時刻から24時間以内しか 指定できないため、2暦日を1回では取得できない。
- 呼び出し回数は最大2回である。所要時間
flightPeriod.plannedFlightTimeの上限が1440分(OASのFlightPeriod.plannedFlightTimeのmaximum)であり、飛行が3暦日にまたがることがないためである。 上限を変更する場合は本項の呼び出し回数も見直す。 - 検索範囲は、飛行領域を外側へ100m広げた範囲とする。円形(
CIRCLE)は半径に100mを加算し、 多角形(POLYGON)は外側へ100mのバッファを適用する。飛行経路(ROUTE)は、生の経路ではなく バッファ幅FLIGHT_PLAN_AREA_ROUTE.buffer_mを適用済みの飛行領域 (FLIGHT_PLAN_AREA.operational_intent_geometry)へ100mのバッファを適用する。 経路端・折れ点の 角は経路からbuffer_mより遠くにあるため、生の経路にbuffer_m+100mを適用すると角の外側を 取りこぼし、本節の「変換による誤差は常に検索範囲・検索時間帯が広くなる方向に取る」に反する。 広げるときのバッファ生成パラメータも飛行領域と同じものを使い、角を丸めず角のまま外へ出す (丸めると円弧の近似で内側に入る)。 - 上記の形状変換の詳細、および時刻形式・検索時間帯の上限・検索開始時刻の下限といった DIPS側の制約への変換規則は flight-plan-er.mdの 「収集時の検索条件はDIPS参照APIの制約に合わせて変換する」に従う。
- 変換による誤差は常に検索範囲・検索時間帯が広くなる方向に取る。狭くなる方向に丸めると収集漏れとなり、 地図上に存在すべき他の飛行計画が現れないためである。
- 検索開始時刻の下限(システム日付の1日前)へ切り上げるのは検索開始側だけとし、検索終了側は
動かさない。 検索終了側まで動かすと、検索時間帯が広くなるのではなく飛行日から離れた暦日へ移動し、
飛行時間帯に重ならない飛行計画を収集してしまう(前項の方針は範囲を広げることだけを許す)。
暦日全体が下限より過去になる暦日は落とし、検索できる暦日が1つも無い場合はDIPSを呼び出さず
収集結果0件として扱う(1-5-4と同じ扱いとし、リンクの全削除だけを行う)。飛行終了APIが未実装のため、
飛行日を過ぎても
ACCEPTEDのまま残る飛行計画で発生しうる。 - 下限はJSTの暦日境界ではなく、DIPS側のタイムゾーンの暦日で決まる値をそのまま使う。 「システム
日付の1日前」の判定はDIPSが自身のタイムゾーンで行うため、JSTの0時へ切り上げても、ワイヤ上の暦日は
その1日前になりうる(模擬DIPSはUTCで、JSTの0時はUTCの前日15時にあたる)。下限の算出はDIPS側の
タイムゾーンを知る層(
DipsApiClientの実装)に委ね、検索時間帯の組み立てはその値へ切り上げる。 切り上げた結果、先頭の暦日だけは0時始まりにならない。 - 暦日ごとに分けた呼び出し全体を1回の収集操作として扱う(1-5の
collected_atの扱いも同じ)。 同じ飛行計画が両方の暦日で返る場合は、dips_flight_plan_idをキーにマージする。
- 検索範囲・検索時間帯は、
- 1-3. DIPS API(飛行計画参照API・範囲指定検索)を呼び出す。
- 呼び出しはトランザクションの外で行い、応答をすべて受け取ってから1-4以降の保存を1トランザクションで 実行する。呼び出しが失敗した場合はデータベースを変更しない(一部の時間帯だけが更新された収集結果を 残さない)。
- 参照APIは副作用を持たないため、失敗時はOperatorが本APIを再実行できる。
- 1-4. 応答から自組織が通報した飛行計画を除外する。
- 参照APIは自組織の計画と他の計画を区別せずに返すため、応答の飛行計画IDを自組織の通報履歴
(
DIPS_REPORT.dips_receipt_no。FLIGHT_PLAN.organization_idで自組織に絞る)と突き合わせ、 一致するものは保存対象から除く。 - 同一UTM上の他組織の飛行計画は除外せず収集対象とする。理由は flight-plan-er.mdの 「除外するのは収集起点の組織が通報した計画だけとする」を参照。
- 参照APIは自組織の計画と他の計画を区別せずに返すため、応答の飛行計画IDを自組織の通報履歴
(
- 1-5. 収集結果をデータベースに保存する。更新対象は
DIPS_FLIGHT_PLAN(収集した飛行計画)とFLIGHT_PLAN_DIPS_NEARBY_LINK(起点の飛行計画リビジョンとのリンク)の2テーブルで、 テーブルごとに更新方法を変える。 以降の操作はすべて同一トランザクションで行う。- 1-5-0. 保存に先立って対象の飛行計画行をロック(
SELECT ... FOR UPDATE)し、1-1で収集の起点と した状態・現行リビジョンのままであることを再確認する。 1-3のDIPS呼び出しはトランザクションの 外で行うため、1-1の取得から本項までの間に飛行計画更新・飛行終了・飛行取消が割り込みうる (implementation-guide.mdのADR-022「遷移2: 応答で確定(別Tx、 再取得。並行で状態が変わりうる)」)。再確認しないまま保存すると、1-5-2の全削除が割り込んだ更新に よるリンクの作成・削除を踏み越え、古いリビジョンに紐づくリンクを作り直してしまう。flight_plan_id・flight_plan_revision_idのFK制約はいずれも満たすため検出されず、1-1の 「取りこぼす方向の誤りは、存在しない周辺計画を地図に出し続ける」がそのまま起きる。- 論理削除されていた場合は404、状態が
ACCEPTED・ACTIVATED以外になっていた場合、または 現行リビジョンが入れ替わっていた場合は409を返し、データベースを変更しない。参照APIは副作用を 持たないため、Operatorは新しい状態に対して本APIを実行し直せる。
- 論理削除されていた場合は404、状態が
- 1-5-1.
DIPS_FLIGHT_PLANはdips_flight_plan_id(DIPS側の飛行計画ID)をキーにしたUPSERTで 保存する。UTMが発行するDIPS_FLIGHT_PLAN.idは再収集でも振り直さない(GeoSpatialが外部公開IDとして 返すため)。行の削除は行わない(削除すると同じDIPS計画に別のIDが振り直される)。- 個人情報は保存しない。DIPS応答のうち連絡先・操縦者情報にあたる項目は、応答原文を保持する
raw_payloadからも除去する。運航調整で必要な相手の連絡先は運航調整ドメインが保持する。 - 認定USP名称・認定USPコードは模擬DIPSの応答に存在しないため、forDemoではダミーの固定値を 設定する(4.5。値は同節を正とする)。
- AMSL/WGS84の換算値(
min/max_altitude_m_amsl・min/max_altitude_m_wgs84・plan_geometry_wgs84・top_bottom_3d_geometry_wgs84)は保存に先立って算出し、UPSERTと あわせて書き込む。 自組織の飛行計画(FLIGHT_PLAN_AREAの登録・更新)は1頂点でも標高データが 当該地点をカバーしていない場合は登録・更新自体を拒否するが、周辺収集は多数の他Operator計画を 一括で取り込むバッチ処理であり、収集した計画はそのまま保存する対象であるため方針が異なる。 1件が標高データのカバー範囲外でもその計画のWGS84/AMSL列だけをNULLのまま保存し、収集全体は 失敗させない(issue #271)。この非対称の結果、GeoSpatialの他Operator計画検索 (OtherFlightPlanAreaFeature)は自組織の計画と異なりaltitudeReference=WGS84のFeatureが 欠落することがある(docs/openapi/frontend/geospatial.yamlのOtherFlightPlanAreaFeatureの description参照)。
- 個人情報は保存しない。DIPS応答のうち連絡先・操縦者情報にあたる項目は、応答原文を保持する
- 1-5-2.
FLIGHT_PLAN_DIPS_NEARBY_LINKは対象の飛行計画に紐づくリンクを全削除したうえで、 応答に含まれた計画分を現行リビジョンに対して登録し直す(差分更新はしない)。- 全削除とするのは、周辺の飛行計画が途中で取り下げられた場合、DIPSの応答から消えるだけで 「取り下げられた」という情報は得られないためである。差分更新では、消えた計画のリンクが 残っているのか応答から漏れただけなのかを判断できず、地図上に存在しない計画が残り続ける。 全削除してから登録し直せば、リンクの集合は常に直近の応答と一致する。
- 削除の範囲は対象の飛行計画(
flight_plan_id)に紐づくリンクに限る。テーブル全体を削除すると 他の飛行計画・他組織の収集結果まで失われ、各Operatorが本APIを実行し直すまで地図に周辺情報が 出なくなる。 - この操作により、旧リビジョンに紐づくリンクも同時に消える(最新リビジョンの収集結果のみを 保持するという方針を、リビジョンの判定なしに満たせる)。
- リンクの
idは毎回新しく採番される。リンクのIDを外部へ公開している箇所はなく(GeoSpatialが返す のはDIPS_FLIGHT_PLAN.id)、外部から参照される値ではないため、DIPS_FLIGHT_PLAN.idのような 安定性は要求しない。
- 1-5-3.
collected_atには、1-2で暦日ごとに2回呼び出した場合も両方の呼び出しをまたいで同一の 収集時刻を設定する(DIPS_FLIGHT_PLAN・FLIGHT_PLAN_DIPS_NEARBY_LINKの双方)。 - 1-5-4. 収集結果が0件の場合もエラーとせず204を返す。この場合リンクは全削除だけが行われ、対象の 飛行計画の周辺には他の飛行計画が存在しない状態としてGeoSpatialに反映される。DIPSが応答を返した うえでの0件と、周辺の計画がすべて取り下げられた結果の0件は区別しない(現況をそのまま反映する)。
- 1-5-0. 保存に先立って対象の飛行計画行をロック(
- 1-6. 冪等性・同時実行の扱い
- 本APIは
Idempotency-Keyヘッダーを受け付けない。再実行しても収集結果は同じ最新状態に収束する (1-5のUPSERTとリンクの全削除・再登録)ため、重複排除を必要としないためである。 - 収集(1-1〜1-4)は排他せず、保存(1-5)だけを対象行のロックで直列化する。 収集まで排他すると
DIPS呼び出しの間ずっと行ロックを保持することになり、同じ飛行計画への更新・通報を待たせる
(ADR-022がトランザクション内の外部I/Oを禁じる理由と同じ)。保存だけを直列化すれば、リンクの
全削除・再登録が
(flight_plan_revision_id, dips_flight_plan_ref_id)のUNIQUE制約に違反することも ない(1-5-0のロックを取るまで削除も登録も始まらないため、2つの実行が同じリビジョンへ同時に 登録する状況が生じない)。 - 収集済み飛行計画(
DIPS_FLIGHT_PLAN)のUPSERTは、DIPSの応答順ではなくdips_flight_plan_idの 昇順で行う。 同テーブルは飛行計画をまたいで共有するため(1-5-1のとおり削除せずIDを維持する)、 周辺が重なる別の飛行計画のリフレッシュが同じ行を更新しうる。応答順のまま書くと2つの トランザクションが同じ行を逆順にロックし、デッドロックになる (implementation-guide.md「ロック取得順序を一定(型/ID 順)にして デッドロックを避ける」)。対象行のロック(1-5-0)は起点の飛行計画にしか効かないため、 こちらは順序で避ける。 - この直列化は保存の整合性だけを保証するものであり、**同一の飛行計画に対する並行実行の結果は 「後にコミットした側が残る」**となる。先にDIPSを呼んだ実行が後からコミットした場合、より古い 収集結果が残りうる。Operatorの明示操作を契機とする低頻度の操作であり、再実行すれば最新の状態に 収束するため、forDemoでは収集時刻による追い越し防止は行わない。
- 本APIは
- 1-7. エラーケースに合わせてステータスコードを返却する。
- 400: パスパラメータ(
flightPlanId)の形式が不正 - 404: 指定された飛行計画IDの飛行計画が存在しない(論理削除済み・他組織の飛行計画を含む。 DIPS呼び出し中に論理削除された場合を含む(1-5-0))
- 409: 対象の飛行計画が
DRAFT・ENDED・CANCELLEDのいずれか(1-1)、またはDIPS呼び出し中に 状態・現行リビジョンが変わった場合(1-5-0) - 502: DIPSの応答を受け取ったが解釈できなかった場合(200以外の応答・型の不一致など)
- 503: DIPSへ到達できなかった、またはDIPSが要求を受理しなかった場合(接続不可・DIPS停止・
クライアント証明書の検証失敗・業務エラー等。いずれもリクエストの成否は確定している。
/problems/dips-call-failed) - 504: DIPS呼び出しがタイムアウトし、応答を受け取れなかった場合。参照APIは副作用を持たないため、
通報(
reportFlightPlan)と異なり状態の滞留は発生せず、そのまま再実行できる
- 400: パスパラメータ(
5.5. UC-PLAN-04 飛行計画を削除する
Section titled “5.5. UC-PLAN-04 飛行計画を削除する”-
- 飛行計画削除
- 1-1. 対象の飛行計画ステータス
FLIGHT_PLAN.statusから削除可否を判定する。- 削除できるのは
DRAFT・ACCEPTED・CANCELLEDの場合である。ACTIVATED(飛行中)・ENDED(終了)を対象に指定された場合は409エラーを返却する。 ENDEDを削除できないのは、飛行終了ではreportStatusがREPORTEDのまま残るため、削除すると実際に行われた飛行のDIPS通報を取り下げることになるからである。CANCELLEDは取り下げ済み(WITHDRAWN)または未通報でDIPSへの副作用がないため削除できる。
- 削除できるのは
- 1-2. 対象がDIPS通報済み(
reportStatusがREPORTED)だった場合、飛行計画自体の削除に先立ってDIPS側に登録されている飛行計画を削除(取り下げ)する。- 1-2-1. DIPS API(飛行計画削除)の呼び出し前に
reportStatusをWITHDRAWINGに更新する(FLIGHT_PLAN_STATE_EVENTにevent_type=WITHDRAW_STARTを追記する)。 - 1-2-2. DIPS APIは実行モード
mode=2(削除)で呼び出す。DIPS側の受付番号としてdipsFlightPlanIdの送信が必須であり、UTMが発行するflightPlanId(UUID)ではない(flight-plan-field-mapping.md参照)。 - 1-2-3. 取り下げが成功した場合は
reportStatusをWITHDRAWNに更新し(event_type=WITHDRAW_COMPLETE)、続けて手順1-3の論理削除を実行する。 - 1-2-4. DIPS APIの呼び出しが失敗した場合、飛行計画自体は削除せず
reportStatusのみを更新して削除失敗を返却する。- 503(認証エラー・接続不可・DIPS停止・業務エラー等。成否が確定した失敗):
reportStatusをREPORTEDに戻し(event_type=WITHDRAW_FAIL)、Operatorが本APIを再試行できるようにする。 - 504(タイムアウトで成否不明):
reportStatusはWITHDRAWINGのまま保持する(event_type=WITHDRAW_TIMEOUTとして事実のみを記録し、状態は変えない)。REPORTEDに戻すとDIPSへ二重の削除要求を送りうるためである(滞留の状態確認手段は未整備。ADR-022の残課題)。
- 503(認証エラー・接続不可・DIPS停止・業務エラー等。成否が確定した失敗):
- forDemoでは本手順は発生しないため実装対象外とする(4.6節参照)。
- 1-2-1. DIPS API(飛行計画削除)の呼び出し前に
- 1-3. 対象の飛行計画を論理削除する。
FLIGHT_PLAN.deleted_atに削除時刻を設定する。物理削除は行わない(報告のための保存期間中は物理削除しないため、およびIdempotency-Keyによる同一キーの再送に対して最初の実行結果を返す契約のため)。飛行計画の更新履歴(FLIGHT_PLAN_REVISION以下の不変レコード)は保持する。statusは変更しないためFLIGHT_PLAN_STATE_EVENTは追記しない(DRAFTからのキャンセルと同じ扱い)。- 対象が
DRAFTの場合は、あわせて一時保存内容(FLIGHT_PLAN_DRAFT)の行を物理削除する。 - 収集済みの周辺飛行計画リンク(
FLIGHT_PLAN_DIPS_NEARBY_LINK)は連動して物理削除する(収集元のDIPS_FLIGHT_PLANは他の飛行計画からも参照されるため残す)。この連動削除は未実装である。 更新時の張り替え(5.4節 手順1-1)とあわせてfollowups.mdの台帳に登録し、別PRで実装する。 - 論理削除以降、当該飛行計画は飛行計画一覧取得・飛行計画詳細取得の対象から外れる(検索処理は
deleted_at IS NULLのデータのみを対象とする)。 - 削除成功時はレスポンスボディを持たない 204 を返却する。
- 1-4.
Idempotency-Keyヘッダーが指定されている場合はデータベースに保存する。- DIPSへ二重の削除要求を送ることを防ぐため、
Idempotency-Keyヘッダーで同一キーの再送(1-2-4の再試行・ネットワークリトライ等)では削除を重複実行せず、最初の実行結果を 204 で返す。同一キーを異なる飛行計画IDに対して再利用した場合は 422 を返す。
- DIPSへ二重の削除要求を送ることを防ぐため、
- 1-5. エラーケースに合わせてステータスコードを返却する。
- 400: パスパラメータ(
flightPlanId)のパース失敗 - 404: 指定された飛行計画IDの飛行計画が存在しない(論理削除済みの飛行計画を
Idempotency-Keyなしで再度削除しようとした場合を含む。手順1-3のとおり削除済みは検索処理の対象外となるため) - 409: 対象の飛行計画が
ACTIVATED・ENDEDの状態(状態遷移違反)、または他の処理によるロック中でアクセス不可(typeで区別) - 422:
Idempotency-Keyを異なる飛行計画IDに対して再利用した場合 - 503: DIPS API(飛行計画削除)の呼び出しが失敗し成否が確定した場合(認証エラーとその他を
typeで区別) - 504: DIPS API(飛行計画削除)の呼び出しがタイムアウトし成否が確定できない場合
- 400: パスパラメータ(