CoTブリッジの作り方:NVR・AIS・ドローンのデータを、地図を埋め尽くさずにTAKへ載せる

October 1, 2026

私たちがスコープを確認するTAK案件は、最終的に必ず同じ問いにたどり着きます。サーバーは立ち上がり、現場チームのスマートフォンにはATAKが入り、地図も動いている。そこで誰かが聞くのです。「なぜゲートのカメラも、船舶のトラッカーも、ドローンも地図に出ていないのか」と。

理由は単純で、それらはTAKの言葉を話さないからです。TAKの地図が理解できるのはCursor on Target(CoT)イベントだけで、TAKのエコシステム外の機器がCoTをそのまま出力することはほとんどありません。NVRは独自のWebhookでナンバー読み取り結果を送り、AIS受信機はNMEAセンテンスを出力し、ドローンの地上局はMAVLinkやメーカー独自のAPIで情報を流します。その間に立って各ソースをCoTに変換し、しかも地図を情報で溺れさせず有用なまま保つ仕組みが必要です。それがCoTブリッジであり、センサーの多いTAK案件では、実際のエンジニアリング作業の大半はここにあります。

これまでの記事ではTAKの仕組みと、複数拠点でOpenTAKServerを運用するハブ・アンド・スポーク構成を扱いました。本稿はその一段下、本番運用に耐えるブリッジが何を決めなければならないか、どう接続すべきか、そして実際に動かせるサンプルを解説します。

1. ブリッジが実際にやっていること

ブリッジの仕事は4つで、順番はこうです。

flowchart TD
    SRC["ソースのデータ NVRのWebhook AIS ドローンのテレメトリなど"] --> PARSE["生データの解析と検証"]
    PARSE --> FILTER["低信頼度と対象エリア外のデータを除外"]
    FILTER --> MAP["UID type stale時刻を持つCoTイベントへ変換"]
    MAP --> RATE["レート制御 重複排除とUIDごとの最新値優先"]
    RATE --> TLS["相互TLSでTAKサーバーへ送信"]
    TLS --> CLIENTS["ATAK WinTAK iTAK"]
    RATE --> BUF["サーバーとのリンク断の間はローカルにバッファ"]
    BUF --> TLS

ソース固有の形式を解析し、誰にも見せるべきでないもの——信頼度の低い検知、対象エリア外の位置、テスト用のトラフィック——を除外し、残ったものをCoTイベントに変換します。設計判断の大半はこの変換にあります。センサーは人が地図を読み取れる頻度よりはるかに頻繁に報告するので、レートを制御します。そして認証済みの接続で送信し、接続が切れている間はバッファします。

うまくいかないブリッジの多くは、除外とレート制御を飛ばしています。忠実に変換し、すべてを送り、1週間後にはオペレーターが地図が読めないという理由でそのレイヤーを非表示にしてしまうのです。

2. CoTイベントの構造

CoTイベントは小さなXML文書です。サンプルのブリッジが1回のナンバー読み取りから生成するイベントは次のとおりです。

<event version="2.0" uid="anpr-gate-north-1KB1234" type="a-u-G-E-V" how="m-p"
       time="2026-10-01T04:14:14Z" start="2026-10-01T04:14:14Z" stale="2026-10-01T04:24:14Z">
  <point lat="13.7563" lon="100.5018" hae="0" ce="15.0" le="9999999"/>
  <detail>
    <contact callsign="Plate 1KB1234"/>
    <remarks>ANPR gate-north confidence 0.93</remarks>
  </detail>
</event>
フィールド 意味 ブリッジが決めるべきこと
uid 報告対象の識別子。報告をまたいで不変 現実の対象ごとに安定したIDをどう作るか
type 階層的な分類。aは物理的な対象、続いて所属(f味方、h敵、n中立、u不明)、次に領域(G地上、A空中、S水上、U水中)、さらに具体的な機能 センサーが正直に主張できるアイコンと所属はどれか
how 報告の生成方法。mは機械生成、hは人による入力 センサーのデータならほぼ常に機械生成
time / start / stale イベントの生成時刻、状態が有効になった時刻、現在の情報として扱うのをやめる時刻 各ソースの報告はどれだけの間「真」なのか
point 緯度、経度、WGS84楕円体高(hae)、水平・垂直方向の1シグマ誤差(m)(ce、le) 位置は本当にどれだけ正確か
detail 拡張用の自由な要素。contact、track、remarksなど、クライアントが解釈できるもの オペレーターがアイコンをタップしたときに何を見せるか

誤差が「不明」であることは非常に大きな値で表すのが慣例で、PyTAK自身も実際の値がないときは9999999を出力します。精度をでっち上げるよりはるかに健全です。

3. すべてのブリッジが下す4つの判断

UID:現実の1つの対象に、1つのUIDを、ずっと

UIDによってTAKは、新しい報告が新規アイコンではなく既存アイコンの更新であることを知ります。ここを誤ると2種類の失敗が起こります。報告のたびに新しいアイコンが増える(同じ船が何百隻も並ぶ)か、現実には別の複数の対象が1つのアイコンにまとまってしまうかです。

UIDはソースが持つ最も安定した識別子から作ります。AISなら船舶のMMSI、ドローンなら便名ではなく機体シリアルやリモートIDです。ANPRの場合は地図に何を見せたいかで変わります。カメラ+ナンバーならゲートごと車両ごとに1つのアイコン、ナンバーだけならゲート間を移動する1つのアイコンになります。どちらも筋は通りますが、意図して選ぶこと、そしてソースごとに接頭辞(ais-、anpr-、uas-)を付けて異なるソース同士が衝突しないようにすることが重要です。

日本語のナンバー(「品川 300 あ 12-34」のような地名とひらがなを含む表記)やタイ語のナンバーには、もう一手間必要です。XMLはこれらの文字を問題なく扱えますが、UIDはあらゆるクライアントやツールで確実に扱えるASCIIにしておくのが安全です。UIDには取り決めたASCIIコードやハッシュ値を使い、実際のナンバー表記はオペレーターが実際に目にするcallsignに入れてください。

type:センサーが実際に知っていることだけを主張する

ナンバー認識カメラにわかるのは、そこに車両があるということだけです。その車両が味方か敵かはわからないので、正直な所属はu(不明)、typeは地上車両(a-u-G-E-V)になります。同じ規律はどこでも当てはまります。AISの目標は、権限のある誰かが判断するまでは所属不明の水上目標であり、自分たちのドローンは味方の空中目標です。目立たせるためにすべてを「敵」にするブリッジは、オペレーターに赤を無視する習慣をつけさせてしまいます。

stale時刻:この報告はいつまで真なのか

stale時刻は、多くのブリッジで最も設計が甘いフィールドであり、同時にオペレーターの見え方に最も影響するフィールドです。stale時刻を過ぎると、クライアントはそのアイコンを現在の情報として扱わなくなります。ソースごとに、報告頻度と対象の移動速度から決めてください。

ソース 一般的な更新頻度 妥当なstale時刻
ドローンのテレメトリ 約1秒に1回 数十秒。報告が止まったドローンはそれ自体がニュース
AISの船舶 速度と船種により数秒〜数分 数分。錨泊中の船舶はより長く
ANPRの読み取り 通過1回につき1イベント 数分。ライブ追跡ではなく目撃の記録
固定センサーの警報 状態変化時 警報が解除されるまで。ハートビートで更新

長すぎるstale時刻は地図に「幽霊」を残し、短すぎると更新の合間にアイコンが点滅して消えたり現れたりします。どちらもテスト端末1台のラボ環境では見えないため、見落とされがちです。

位置誤差:持っている不確かさをそのまま報告する

ナンバーの読み取り位置は車ではなくカメラの位置なので、ceはゼロではなくカメラの撮影範囲の半径にすべきです。AISの位置にはそれ自体の精度があり、人の報告から推定した位置はさらに誤差が大きくなります。クライアントはceを使って不確かさを表示できますが、それはブリッジが正直に値を入れた場合に限ります。

4. レート制御:地図を読める状態に保つ部分

交通量の多いゲートのカメラ1台だけで、1時間に数百件の読み取りが発生し得ます。その多くはカメラの前でアイドリングしている同じ車両です。ドローンは1秒間に何度もテレメトリを送ります。4G回線上のTAKクライアントは、どちらもそのレートでは必要としていません。

通常は次の3つを組み合わせます。

  • エッジで捨てる。 信頼度のしきい値を下回る検知や対象エリア外のデータは、帯域を消費する前に破棄します。
  • UIDごとに重複排除する。 同じ対象をN秒以内に送っていて、意味のある移動がなければスキップします。
  • 最新値を優先する。 送信キューが詰まったら、古い位置の滞留分を順番に送るのではなく、UIDごとに最新のイベントだけを残します。PyTAK自体のデフォルト動作もこの方向で、送信キューは100イベントを保持し、満杯になると最も古いものから破棄します。

5. 送信:平文でもHTTPでもなく、相互TLS

OpenTAKServerの標準的な構成では、平文のCoTストリーミングがポート8088、相互TLSのCoTストリーミングが8089、証明書の自動登録が8446で公開されます。本番のブリッジは、自身のクライアント証明書を使って8089に接続すべきです。

これは暗号化以上の意味を持ちます。相互TLSなら、サーバーは各イベントをどのブリッジが送ったかを把握でき、ホストが侵害された場合はそのブリッジの証明書だけを失効でき、各ブリッジをTAKのグループに割り当てて、ある拠点のカメラ映像情報が全国のすべてのチームに配信されるのを防げます。平文の8088を使うブリッジは匿名であり、そのポートに到達できるものなら何でも、オペレーターの地図にアイコンを差し込めてしまいます。

関連する2点:

  • XMLかprotobufか。 TAKのクライアントとサーバーは、従来のXMLエンコーディング(「version 0」)からProtocol Buffersエンコーディング(「version 1」)へのアップグレードを交渉できますが、すべてのクライアントはXMLのデコードに対応し続ける必要があります。ブリッジはXMLで送り、残りはサーバーに任せて構いません。protobufに切り替えるのは、ブリッジ自身の回線の帯域が本当に厳しい場合だけで十分です。
  • 切断時のバッファ。 エッジ拠点の上り回線は切れるものです。ブリッジは容量を制限したローカルバッファを持ち、UIDごとの最新値優先を適用して、再接続時に送り出すべきです。すべてを失うのでも、1時間分の位置を順番に再送するのでもなく。これはハブ・アンド・スポークの記事で取り上げたフェデレーションの問題の、ブリッジ版です。

6. 動作するサンプル:ANPRの読み取りをCoTへ

以下は、NVRからナンバー読み取りを受け取り、除外と重複排除を行い、オープンソースのPython向けTAKライブラリPyTAKでCoTを送信する、最小限ながら完結したブリッジです。ローカルの受信側に対してテストしたところ、3件のサンプル——正常な読み取り1件、その直後の同じナンバー、信頼度の低い読み取り——のうち、送信されたCoTイベントは想定どおり1件だけでした。

"""Minimal ANPR-to-CoT bridge: NVR plate reads in, CoT events out to a TAK server."""
import asyncio
import time
import xml.etree.ElementTree as ET
from configparser import ConfigParser

import pytak

# Fixed camera positions come from the site survey, not from the NVR payload.
CAMERAS = {
    "gate-north": {"lat": 13.7563, "lon": 100.5018, "ce": 15.0},
}
MIN_INTERVAL_S = 30   # same plate at the same camera: at most one update per 30 s
STALE_S = 600         # a plate sighting stops being "current" after 10 minutes

def anpr_to_cot(read: dict) -> bytes:
    """Map one NVR plate read to a CoT event."""
    cam = CAMERAS[read["camera_id"]]
    plate = read["plate"].replace(" ", "").upper()
    event = ET.Element(
        "event",
        version="2.0",
        uid=f"anpr-{read['camera_id']}-{plate}",   # stable per plate and camera
        type="a-u-G-E-V",                          # unknown ground vehicle
        how="m-p",                                 # machine-generated, relayed by the bridge
        time=pytak.cot_time(),
        start=pytak.cot_time(),
        stale=pytak.cot_time(STALE_S),
    )
    ET.SubElement(event, "point", lat=str(cam["lat"]), lon=str(cam["lon"]),
                  hae="0", ce=str(cam["ce"]), le="9999999")
    detail = ET.SubElement(event, "detail")
    ET.SubElement(detail, "contact", callsign=f"Plate {plate}")
    ET.SubElement(detail, "remarks").text = (
        f"ANPR {read['camera_id']} confidence {read['confidence']:.2f}"
    )
    return ET.tostring(event)

class AnprSender(pytak.QueueWorker):
    """Reads plate events from a local queue, rate-limits per UID, sends CoT."""

    def __init__(self, tx_queue, config, source: asyncio.Queue):
        super().__init__(tx_queue, config)
        self.source = source
        self.last_sent: dict[str, float] = {}

    async def handle_data(self, data: bytes) -> None:
        await self.put_queue(data)              # hand the CoT event to pytak's TX worker

    async def run(self, number_of_iterations=-1):
        while True:
            read = await self.source.get()
            if read["confidence"] < 0.80:      # drop low-confidence reads at the edge
                continue
            key = f"{read['camera_id']}-{read['plate'].replace(' ', '').upper()}"
            now = time.monotonic()
            if now - self.last_sent.get(key, 0) < MIN_INTERVAL_S:
                continue                        # duplicate within the window
            self.last_sent[key] = now
            await self.handle_data(anpr_to_cot(read))

async def main(cot_url: str, reads: list[dict]):
    config = ConfigParser()
    config["bridge"] = {"COT_URL": cot_url}
    # For mutual TLS on port 8089, also set PYTAK_TLS_CLIENT_CERT,
    # PYTAK_TLS_CLIENT_KEY and PYTAK_TLS_CLIENT_CAFILE here.
    source: asyncio.Queue = asyncio.Queue()
    for r in reads:
        source.put_nowait(r)
    clitool = pytak.CLITool(config["bridge"])
    await clitool.setup()
    clitool.add_tasks({AnprSender(clitool.tx_queue, config["bridge"], source)})
    await clitool.run()

注目すべき点は3つです。

  • 位置はNVRではなく現地調査から。 ほとんどのNVRは自分がどこにあるかを知らないため、カメラの座標と撮影範囲の半径はブリッジの設定に持たせます。
  • handle_dataは自分で実装する必要がある。 PyTAKのQueueWorkerではこれはプレースホルダーにすぎず、イベントを送信キューに入れる処理を書く必要があります。私たちはこれを実際のテストで知りました。最初の実行では、PyTAK自身の接続用pingしか送られていなかったのです。
  • TLSはコードではなく設定の問題。 COT_URLをtls://your-server:8089にし、PYTAK_TLS_CLIENT_CERT、PYTAK_TLS_CLIENT_KEY、PYTAK_TLS_CLIENT_CAFILEにブリッジの証明書、鍵、サーバーのCAを指定します。PYTAK_TLS_DONT_VERIFYはデフォルトの0のままにしてください。

本番版では、サンプルのリストの代わりにWebhookの受信部を置き、永続バッファ、稼働状態のメトリクス、そしてブリッジ自体の報告が止まったときにオペレーターがそれに気づけるハートビートイベントを追加します。

7. ブリッジの運用

ブリッジは、一度でも誤った情報を見せれば信頼を失うサービスです。他の本番サービスと同じ配慮が必要です。

  • ブリッジ自体を見えるようにする。 ブリッジ自身のハートビートCoTを送信します。ある拠点のブリッジが止まったとき、オペレーターにはそのアイコンがstaleになるのが見えるべきで、レイヤーが黙って消えるのではいけません。
  • 送ったものだけでなく、捨てたものを記録する。 「なぜあの車両が地図に出なかったのか」と聞かれたとき、答えはたいていどれかのフィルターです。どれだったかを示せる必要があります。
  • 変換ルールをバージョン管理する。 typeコード、stale時刻、しきい値は運用上の判断です。設定としてバージョン管理し、変更をレビュー・ロールバックできるようにします。
  • ブリッジ1つに証明書1つ。 ブリッジ間や拠点間で証明書を共有してはいけません。失効が機能するのは、1枚の証明書が1つのものにだけ対応している場合です。

8. 日本企業としての視点:タイ拠点の構内警備と個人情報保護法

タイやASEANの工業団地に工場を持つ日系企業では、構内警備の高度化の一環として、ゲートのナンバー認識、周界センサー、巡回要員の位置を1枚の地図に集約したいという要望が増えています。CoTブリッジはまさにその接着剤ですが、ナンバー情報は所有者と結び付けば個人に関する情報になります。現地法(タイのPDPA)への対応に加え、日本の本社が個人情報保護法の観点から子会社のデータ管理を確認する場合にも、ANPRレイヤーの閲覧権限を限定されたTAKグループに絞り、stale時刻を短く設定して地図を「移動履歴」ではなく「現在の状況図」に保つ設計は、説明しやすい統制になります。長期の記録が必要なら、保存期間のルールが明確な別システムに置くべきで、作戦地図の役割ではありません。

9. Simplicoが構築するもの、ミッションごとにスコープを決めるもの

ブリッジの構築は、TAK Integrationページに記載している固定スコープの案件です。定義したソース群のためのブリッジを、ソースコードの納品と引き継ぎ込みで提供します。よく接続するソースは、ドローン・UAVのテレメトリ、CCTV/NVRとANPRのイベント、GPSトラッカー、AIS、ADS-B、周界センサー、SCADAの警報です。

ミッションごとにスコープを決めるもの: ATAKやWinTAKのカスタムプラグイン(映像タイルやミッション専用の画面など)、ライブ映像の配信経路、protobufでの送信、そしてサイバー検知を地図上のイベントに、現場のインシデントをケースにするSOCとTAKの双方向連携です。いずれも独立した作業で、ソースと運用環境を理解したうえで見積もります。

対象外: センサーそのものです。お客様がすでに保有している、あるいは調達するカメラ、受信機、ドローンを接続します。ハードウェアの供給や上乗せ販売は行いません。

よくある質問

TAKサーバーにHTTPでPOSTするだけではだめですか。

試作ならそれで構いません。本番では相互TLSによるストリーミング接続のほうがよい既定値です。ブリッジごとに識別でき、グループ単位の配信制御と失効に対応でき、高頻度のソースでHTTPのリクエストごとのオーバーヘッドも避けられます。

ブリッジはエッジ拠点で動かすべきですか、中央ですか。

ソースがエッジにあるならエッジです。センサーの近くで除外と重複排除を行えば上り回線を節約でき、回線断の間もバッファできます。中央のブリッジは、AISの集約フィードのように、もともと中央に集まっているソースに向いています。

どのTAKサーバーで使えますか。

ブリッジはTCPまたはTLS上で標準的なCoTを送るので、OpenTAKServer、FreeTAKServer、公式のTAK Serverのいずれでも使えます。ポートや証明書登録の方法はサーバーによって異なり、本稿の値はOpenTAKServerのものです。

センサーごとにブリッジが必要ですか。

通常はソースの種類ごとに1プロセス——ANPR用、AIS用、ドローン用——が最も整理しやすい形です。それぞれレート、staleの特性、障害の起き方が異なるためです。コードベースとデプロイは共通にできます。


TAKの地図に載るべきなのにまだ載っていないセンサーがあるなら、私たちの2週間のレディネス評価で、何かを作り始める前に各ソースとその頻度、接続状況を洗い出します。お問い合わせはhello@simplico.net、電話(+66) 97 496 6397、WhatsApp (+66) 83 001 0222、LINE ID: iiitum1984まで。


出典:

Ready to talk about your project?

Share goals and constraints. We'll assemble architects and engineers to move fast with you.

Get in touch