Upgrade to Pro — share decks privately, control downloads, hide ads and more …

海上で動くGoサーバー: goroutineとchannelでさばく航行データストリーム

Avatar for Atsuki Seo Atsuki Seo
September 11, 2026

海上で動くGoサーバー: goroutineとchannelでさばく航行データストリーム

Go Conference 2026 登壇時の資料になります。

https://gocon.jp/2026/timetable/1251905/

#gocon26 #gocon26A

Avatar for Atsuki Seo

Atsuki Seo

September 11, 2026

More Decks by Atsuki Seo

Other Decks in Programming

Transcript

  1. 自己紹介 • 名前:瀬尾 敦生(せお あつき) • 出身:広島県福山市 • 職業: –

    主)iOSアプリエンジニア – 副)弓削商船高専 非常勤講師 Goのコードは普段は あまり触りません 2026年9月11日(金) Go Conference 2026 海上で動くGoサーバー: goroutineとchannelでさばく航行データストリーム 2
  2. 目次 • 自己紹介 • 背景 -海の法律と現行技術について- • 構想 -海上サーバーの構想と基礎技術について- •

    考察 -Go言語での実装案について- • 今後の課題 2026年9月11日(金) Go Conference 2026 海上で動くGoサーバー: goroutineとchannelでさばく航行データストリーム 5
  3. 背景 -小型船舶に乗る人は多い• • 毎年5万人前後の人が小型船舶の 運転免許をとっている – 2021年は多かったので7.6万人 – ピーク時より減少傾向はあるが、 まだ船に乗る人が多い

    1級・2級・特殊の免許区分。 2級は5海里まで、1級は全海域。 – 1海里 = 1.852km 2026年9月11日(金) Go Conference 2026 [引用] https://www.mlit.go.jp/kowan/content/001628057.pdf 海上で動くGoサーバー: goroutineとchannelでさばく航行データストリーム 7
  4. 背景 -海上衝突予防法• 前後左右に動ける船が事故 なくスムーズに 流れるための法律は 定義されている – • 海上衝突予防法 避航船(避ける側)は

    早めに大きな動きで 他船の進路を大きく 避ける必要があり 2026年9月11日(金) [引用] https://www6.kaiho.mlit.go.jp/watersafety/miniboat/01_skill/ Go Conference 2026 海上で動くGoサーバー: goroutineとchannelでさばく航行データストリーム 10
  5. 背景 -海上衝突予防法• • 回避すべき船舶 – 運転不自由船 – 操縦性能制限船 – 漁ろうに従事している船舶

    – 帆船 小型船舶は上記に加えて、 大型船の運行を妨げないよう 配慮する義務もある [引用] https://laws.e-gov.go.jp/law/352AC0000000062 [引用] https://www.mlit.go.jp/maritime/content/001859196.pdf 2026年9月11日(金) Go Conference 2026 海上で動くGoサーバー: goroutineとchannelでさばく航行データストリーム 11
  6. 背景 -事故防止の仕組み: センサー• • GPS、 風向・風速、 水深・船速、 などのセンサーと 専用の表示機が 市販されてる

    簡易的なアラートが ついた製品も多くある [引用] https://www.furuno.com/jp/products/instruments/fi-70/index.html 2026年9月11日(金) Go Conference 2026 海上で動くGoサーバー: goroutineとchannelでさばく航行データストリーム 12
  7. 背景 -事故防止の仕組み: レーダー• • • 電波の反射波で障害物や 他船の位置を把握する ブイや網などの物標に対して も効果がある 地形によっては検知できない

    – 例)岩陰 [引用]https://www.furuno.com/jp/products/radar/fr-10-12/index.html 2026年9月11日(金) Go Conference 2026 海上で動くGoサーバー: goroutineとchannelでさばく航行データストリーム 13
  8. 背景 -事故防止の仕組み: AIS• • • VHF無線で船の位置情報 をやり取りする仕組み 地形によらず電波が 通じるならやり取り可能 大型船に搭載義務がある

    – 小型船に搭載義務はない [引用] https://www.furuno.com/jp/products/ais/fa-170/index.html 2026年9月11日(金) Go Conference 2026 海上で動くGoサーバー: goroutineとchannelでさばく航行データストリーム 14
  9. 背景 -事故防止の仕組み: AIS• • 電波を介して データをやり取り する仕組みで、 位置情報以外も 送受信できる 陸でも受信できる

    – 海上保安庁も 航行支援に利用 2026年9月11日(金) [引用] https://www.kaiho.mlit.go.jp/10kanku/ais-kagoshima/ais.html Go Conference 2026 海上で動くGoサーバー: goroutineとchannelでさばく航行データストリーム 15
  10. [引用] https://arundaleais.github.io/docs/ais/ais_message_types.html# 背景 -事故防止の仕組み: AIS• • 27種類の通信 タイプが定義さ れている 位置情報と

    静的情報は 分けられてる – 静的 = 名前、行き先 船の種別 2026年9月11日(金) Go Conference 2026 海上で動くGoサーバー: goroutineとchannelでさばく航行データストリーム 16
  11. 背景 -小型船舶航行支援 既存研究/サービス• • • コストが高いことを理由に前述の 設備たちをフルに載せる船は少ない スマートフォンで代替できれば 安く事故が減らせそう –

    → 一定成果がありそうだった。 – → 国土交通省も仕様策定 携帯が電波不感エリアだと 無力になる 2026年9月11日(金) Go Conference 2026 海上で動くGoサーバー: goroutineとchannelでさばく航行データストリーム 17
  12. 背景 -小型船舶航行支援 既存研究/サービス• • スマホのキャリアの電波は 陸上を想定している – 右は実際に海上での検証結果 – 主要海域で3~4割ロスト

    衛星通信も選択肢に出たが リアルタイムな判定に 使えるほどの速度・品質? 2026年9月11日(金) Go Conference 2026 海上で動くGoサーバー: goroutineとchannelでさばく航行データストリーム 18
  13. 背景 -小型船舶航行支援 既存研究/サービス• • 流通してる船で、装備品に GPS、魚群探知機などが すでにあるものもいる これらのセンサを活用し、 海上でオフラインでも 動く仕組みがあると

    良さそう。 [引用] https://www.boatsensor.com/item/MH0069.html 2026年9月11日(金) Go Conference 2026 海上で動くGoサーバー: goroutineとchannelでさばく航行データストリーム 19
  14. システムの構想 -船上センサー x スマートデバイスアンテナ、センサ 中央サーバー センサーのデータを 処理、安全かを判断 (必要に応じて) スマートデバイスへ 配信する

    [引用] https://www.biggame-lures.com/item/ASC/IT-ASC-MVHF-ATNB-SET.htm [引用] https://www.switch-science.com/pages/raspberry-pi?srsltid=AfmBOop4L2U3P8h0mOJI2IkcsE9ixljkDnHzz1kPPGUBdipEtiALAuAh 2026年9月11日(金) Go Conference 2026 海上で動くGoサーバー: goroutineとchannelでさばく航行データストリーム 21
  15. システムの構想 • • 今回は船の上のアンテナ、センサーで取得で きる情報を元に安全航行支援を考える 国土交通省の基準をベースとする – 自船の状態 • 位置

    • 傾き – 他船の位置・針路(進む方向) – 天候 • 波の高さ / 水深 • 風向・風速 2026年9月11日(金) Go Conference 2026 [引用] https://www.mlit.go.jp/common/001176065.pdf 海上で動くGoサーバー: goroutineとchannelでさばく航行データストリーム 22
  16. システムの構想 • 国土交通省の基準のうち、航海中の支援については以下がある 他船接近警告 危険海域接近 航行支援情報提供 自船の500m以内に 他船がいると警告を出す 自船が危険海域の500m以内に いたら警告を出す

    波高、風向、風速などの 天候情報の案内 2026年9月11日(金) Go Conference 2026 [引用] https://www.mlit.go.jp/common/001176065.pdf 海上で動くGoサーバー: goroutineとchannelでさばく航行データストリーム 23
  17. システムの構想 -メリット・デメリット• デメリット – 船上のセンサやアンテナを使うため、 スマートフォンのみの仕組みよりかは 高価になる – (この発表に至った経緯) 船上のデータ分析、サーバーの活用について

    既存事例はある。 安全航行支援については実装例・実例がない 2026年9月11日(金) Go Conference 2026 海上で動くGoサーバー: goroutineとchannelでさばく航行データストリーム 24
  18. システムの構想 -メリット・デメリット• メリット – 船舶のセンサは表示部も基本的に 専用製品だが、この部分は買わなくても 済むようになる – 危険検知処理、ユーザインタフェースなどソ フトウェア部分のアップデートが容易に行え

    るようになる – 海上ビッグデータの可能性 – AI操船への可能性向上 2026年9月11日(金) Go Conference 2026 海上で動くGoサーバー: goroutineとchannelでさばく航行データストリーム 25
  19. [引用] https://www.furuno.com/jp/products/compass/scx-21/index.html [引用] https://www.furuno.fr/en/lang--en--art--00001048200--FI-5001.html [引用] https://store.shopping.yahoo.co.jp/kai-you/0419204.html [引用] https://www.furuno.com/jp/products/ais/fa-60/index.html [引用] https://www.cqnet.co.jp/musen2/142_7037.html

    システムの構想 -利用するセンサとデータ自船の状態 位置 針路 サテライトコンパス 天候 波の高さ / 水深 風向・風速 水深センサ / 魚群探知機 他船の位置・針路(進む方向) 風向・風速センサ AIS受信機 共通点:NMEA0183という通信規格でやり取りできる 2026年9月11日(金) Go Conference 2026 海上で動くGoサーバー: goroutineとchannelでさばく航行データストリーム 26
  20. [引用] https://en.wikipedia.org/wiki/NMEA_0183 システムの構想 -NMEA0183$GPGGA,092750.000,5321.6802,N,00630.3372,W,1,8,1.03,61.7,M,55.2,M,,*76 送 種 信 類 主 •

    • フィールド チ ェ ッ ク サ ム 上記のフォーマットでセンサのデータをやり取りする規格 センサの受信機 – RS422ケーブル(シリアル通信)で接続すると 取得できる 2026年9月11日(金) Go Conference 2026 海上で動くGoサーバー: goroutineとchannelでさばく航行データストリーム 27
  21. システムの構想 -NMEA0183$GPGGA,092750.000,5321.6802,N,00630.3372,W,1,8,1.03,61.7,M,55.2,M,,*76 ① ② • • ③ ①TalkerID:誰が(どのセンサが)送ってるか – 例)GP=GPS、HC=磁気コンパス、SD=水深計、WI=気象計器

    ②Sentence Formatter(3文字):どんな種類のデータか GGA=位置情報、HDT=方位、DBT=水深、など ③実際のデータ本体 – • 2026年9月11日(金) Go Conference 2026 海上で動くGoサーバー: goroutineとchannelでさばく航行データストリーム 28
  22. システムの構想 -NMEA0183 GGAの例$GPGGA,092750.000,5321.6802,N,00630.3372,W,1,8,1.03,61.7,M,55.2,M,,*76 ① ② ③ ④ ⑤⑥⑦ ⑧ •

    ① UTC 09:27:50 • ⑤西経(東経ならE) • ② 緯度 53度21.6802分 • ⑥単独測位(2=DGPSの補正) • ③ 北緯(南緯ならS) • ⑦使用した衛星の数 • ④ 経度 006度30.3372分 • ⑧海抜高度 2026年9月11日(金) Go Conference 2026 海上で動くGoサーバー: goroutineとchannelでさばく航行データストリーム 29
  23. [引用] https://en.wikipedia.org/wiki/NMEA_0183 システムの構想 -利用するNMEA0183• 自船の状態 – GGA:緯度経度・測位品質 • – –

    MWV: 風向(船首基準) • – 船の進む方向 今受けている風の強さ DPT:水深 • VTG:対地進行速度 • – 天候 船の位置 HDT:船首方位 • – • 喫水が水深に対して余裕があるか (船の沈み込み具合) 船の速度 ROT:回頭角速度 • 船の旋回速度 2026年9月11日(金) Go Conference 2026 海上で動くGoサーバー: goroutineとchannelでさばく航行データストリーム 30
  24. システムの構想 -AIS!AIVDM,1,1,,B,15M67FC000G?ufbE`FepT@3n00Sa,0*5C センテンス フラグ 受信機 種別 メント 種別 数・ 番号

    ペイロード • 上記のフォーマットで船舶の情報をVHFでやり取りする規格 • AISもNMEA0183を使って配信している – !から始まる = バイナリデータを運ぶための専用フォーマット 2026年9月11日(金) Go Conference 2026 海上で動くGoサーバー: goroutineとchannelでさばく航行データストリーム 31
  25. システムの構想 -AIS!AIVDM,1,1,,B,15M67FC000G?ufbE`FepT@3n00Sa,0*5C センテンス フラグ 受信機 種別 メント 種別 数・ 番号

    • • ペイロード AI = TalkerID、送信元。今回の場合だとAISを示す専用のTalkerID VDM = AISの専用の実装であることを示す – VDM = VHF Data-link Message(他船から受信したAISメッセージ) – VDO = VHF Data-link Own-vessel message(自船の送信するAISメッセージ) 2026年9月11日(金) Go Conference 2026 海上で動くGoサーバー: goroutineとchannelでさばく航行データストリーム 32
  26. システムの構想 -AIS!AIVDM,1,1,,B,15M67FC000G?ufbE`FepT@3n00Sa,0*5C ①② ③④ • • • • • ⑤

    ①:総フラグメント数。全部で何個のNMEAセンテンスに分割されてるか。 ②:フラグメント番号。センテンスが何番目の断片か。 ③:シーケンスID。複数センテンスに分かれるメッセージの場合の紐付けID。 ④:利用してるチャンネル種別(AISは2チャンネルで運用されてる)。 ⑤:ペイロード。6bitずつASCII文字に変換する符号化方式を採用している。 2026年9月11日(金) Go Conference 2026 海上で動くGoサーバー: goroutineとchannelでさばく航行データストリーム 33
  27. システムの構想 -AIS!AIVDM,1,1,,B,15M67FC000G?ufbE`FepT@3n00Sa,0*5C ⑤ • ペイロード = 15M67FC000G?ufbE`FepT@3n00Sa ↓ 1文字ずつASCII文字コードを取得し、6bitの値に戻す •

    • • • '1' → 0x31(49) → 49-48=1 → 000001 '5' → 0x35(53) → 53-48=5 → 000101 'M' → 0x4D(77) → 77-48=29 → 011101 'u' → 0x75(117) → 117-56=61 → 111101 ・・・(残りの文字も同様に6bitずつ復元) 2026年9月11日(金) Go Conference 2026 海上で動くGoサーバー: goroutineとchannelでさばく航行データストリーム 34
  28. システムの構想 -AIS!AIVDM,1,1,,B,15M67FC000G?ufbE`FepT@3n00Sa,0*5C ⑤ • ペイロード = 15M67FC000G?ufbE`FepT@3n00Sa ↓ 1文字ずつASCII文字コードを取得し、6bitの値に戻す •

    • '1' → 0x31(49) → 49-48=1 → 000001 先頭の1文字がAISの通信タイプに相当する • このメッセージが位置情報なのか、船舶間のメッセージ通信なのかを示す • Type1,2,3,18がそれぞれ大型船・小型船の位置情報系レポート。 それ以外は今回捨てる 2026年9月11日(金) Go Conference 2026 海上で動くGoサーバー: goroutineとchannelでさばく航行データストリーム 35
  29. システムの構想 -AIS!AIVDM,1,1,,B,15M67FC000G?ufbE`FepT@3n00Sa,0*5C ⑤ • デコードすると右のような情報になる MMSIは船ごとに振られる固有のIDのようなもの という理解でOK • 詳細な説明は割愛します courseは送信元のGPSが算出した向き、

    headingはコンパス由来の算出された向き • { – – 取得できない場合は511(エラー値)。 この例では 1 2026年9月11日(金) } "type": 1, "mmsi": "366053209", "speed": 0.0, "lat": 37.802118, "lon": -122.341618, "course": 219.3, "heading": 1 Go Conference 2026 海上で動くGoサーバー: goroutineとchannelでさばく航行データストリーム 36
  30. Goの採用を検討した経緯 -メリット• Goを採用する場合のメリット – goroutine + channnel による並行処理への耐性 – contextによるキャンセル・タイムアウト処理管理のシンプルさ

    – 通信周りの処理に関して、標準ライブラリでも充実している • シリアル通信 / TCP / UDP などが標準ライブラリでカバーできる 2026年9月11日(金) Go Conference 2026 海上で動くGoサーバー: goroutineとchannelでさばく航行データストリーム 41
  31. Goの採用を検討した経緯 -NMEA / AISのパースライブラリ• NMEAやAISのデータをパースする先行事例は存在する – – • https://github.com/adrianmo/go-nmea •

    ASCIIテキストの処理をするライブラリ • 外部依存ライブラリ:なし https://github.com/BertoldVdb/go-ais • NMEAとしてAISのセンテンスをパース → ビット列への変換とデコードを実施する • 外部依存ライブラリ:go-nmeaのみ 今回はこれを利用する前提で進める – なお、これらのライブラリはあくまでデコードのみ。 受信日時の管理は自前で必要。 2026年9月11日(金) Go Conference 2026 海上で動くGoサーバー: goroutineとchannelでさばく航行データストリーム 42
  32. Goでの実装に関する考察 -NMEA / AISのパースライブラリ• 自船の状態(go-nmeaで定義されてる型、必要な部分のみ抜粋) // 測位・衛星情報 type GGA struct

    { Latitude float64 // 緯度 Longitude float64 // 経度 FixQuality string } // 真方位 type HDT struct { Heading float64 // 方位(度) } // 対地針路・速度 type VTG struct { TrueTrack float64 // COG(真方位) GroundSpeedKnots float64 // SOG(ノット) } 2026年9月11日(金) // 回頭角速度 type ROT struct { RateOfTurn float64 // 度/分。負 = 船首が左(port)に回る Valid bool // "A" = 有効 / "V" = 無効 } Go Conference 2026 海上で動くGoサーバー: goroutineとchannelでさばく航行データストリーム 43
  33. Goでの実装に関する考察 -NMEA / AISのパースライブラリ• 天候の状態(go-nmeaで定義されてる型、必要な部分のみ抜粋) // 風向・風速 type MWV struct

    { WindAngle float64 // 風向(度) Reference string // "R" = 相対 / "T" = 真 WindSpeed float64 // 風速 WindSpeedUnit string // "K" km/h / "M" m/s / "N" knots / "S" mph } // 水深 type DPT struct { Depth float64 // 水深 } 2026年9月11日(金) Go Conference 2026 海上で動くGoサーバー: goroutineとchannelでさばく航行データストリーム 44
  34. Goでの実装に関する考察 -NMEA / AISのパースライブラリ• 他船の状態(go-aisで定義されてる型、必要な部分のみ抜粋) // Type 1/2/3 — Class

    A(大型船) // Field~~系はfloat64(type definition) type PositionReport struct { Header // = ヘッダー部分 Sog Field10 // 対地速度 Longitude FieldLatLonFine // 経度 Latitude FieldLatLonFine // 緯度 TrueHeading uint16 // 針路 } // 通信の共通ヘッダー部分 type Header struct { MessageID uint8 // メッセージ種別 UserID uint32 // 船のID } 2026年9月11日(金) // Type 18 — Class B(小型船・プレジャーボート) // 各フィールドの意味は基本的にPositionReportと同じ type StandardClassBPositionReport struct { Header Sog Field10 Longitude FieldLatLonFine Latitude FieldLatLonFine TrueHeading uint16 } Go Conference 2026 海上で動くGoサーバー: goroutineとchannelでさばく航行データストリーム 45
  35. Goでの実装に関する考察 • システムに対する入力一覧 – サテライトコンパス:1Hz〜 • 位置情報:1Hz〜 • 船首方位:10Hz〜 –

    風向・風速センサ:1Hz〜 – 水深センサ:1Hz〜 – AIS受信機:不定 • 電波・他船の航行状態によるため [引用] https://digitalyacht.net/2026/03/10/nmea0183-display-app/ 各データの入力タイミングは揃わない前提 2026年9月11日(金) Go Conference 2026 海上で動くGoサーバー: goroutineとchannelでさばく航行データストリーム 46
  36. Goでの実装に関する考察 • • シリアル通信(最大3系統) – サテライトコンパス – 風向・風速センサ – 水深センサ

    sources := []Source{ NewSerialSource("compass", "/dev/ttyUSB0", 4800), NewSerialSource("wind", "/dev/ttyUSB1", 4800), NewSerialSource("depth", "/dev/ttyUSB2", 4800), NewUDPSource("ais", ":10110"), } UDP通信(1系統) – AIS受信機 →のコードのイメージで 通信管理が実現できそう 2026年9月11日(金) type Source interface { // Serial, UDP でそれぞれ実装する Name() string // I/O エラーは内部で再接続を試みる Run(ctx context.Context, out chan<- Line) error } for _, s := range sources { go s.Run(ctx, sentences) // sentences chan<- Sentences } Go Conference 2026 海上で動くGoサーバー: goroutineとchannelでさばく航行データストリーム 47
  37. Goでの実装に関する考察 • • シリアル通信(最大3系統) – サテライトコンパス – 風向・風速センサ – 水深センサ

    sources := []Source{ NewSerialSource("compass", "/dev/ttyUSB0", 4800), NewSerialSource("wind", "/dev/ttyUSB1", 4800), NewSerialSource("depth", "/dev/ttyUSB2", 4800), NewUDPSource("ais", ":10110"), } UDP通信(1系統) – AIS受信機 Sentenceの部分を 検討していく必要がある 2026年9月11日(金) type Source interface { // Serial, UDP でそれぞれ実装する Name() string // I/O エラーは内部で再接続を試みる Run(ctx context.Context, out chan<- Line) error } for _, s := range sources { go s.Run(ctx, sentences) // sentences chan<- Sentence } Go Conference 2026 海上で動くGoサーバー: goroutineとchannelでさばく航行データストリーム 48
  38. Goでの実装に関する考察 • 大量のデータが流れてくるが、最終的に欲しいのは以下。 type OwnShip struct { // 自船 Lat,

    Lon float64 Heading float64 SOG, COG float64 Depth float64 WindAngle, WindSpeed float64 UpdatedAt time.Time } type Target struct { // 他船 Lat, Lon float64 Heading float64 SOG, COG float64 UpdatedAt time.Time } Go Conference 2026 // そのタイミングの状態を表す type Snapshot struct { At time.Time // タイムスタンプ Own OwnShip // 自船の情報 Targets map[uint32]Target // 他船の情報 } 2026年9月11日(金) 海上で動くGoサーバー: goroutineとchannelでさばく航行データストリーム 49
  39. Goでの実装に関する考察 • 大量のデータが流れてくるが、最終的に欲しいのは以下。 type OwnShip struct { // 自船 Lat,

    Lon float64 Heading float64 SOG, COG float64 Depth float64 WindAngle, WindSpeed float64 UpdatedAt time.Time } type Target struct { // 他船 Lat, Lon float64 Heading float64 SOG, COG float64 UpdatedAt time.Time } Go Conference 2026 // そのタイミングの状態を表す type Snapshot struct { At time.Time // タイムスタンプ Own OwnShip // 自船の情報 Targets map[uint32]Target // 他船の情報 } 現在の状態に対して 危険検知をしたい 2026年9月11日(金) 海上で動くGoサーバー: goroutineとchannelでさばく航行データストリーム 50
  40. Goでの実装に関する考察 -処理順の定義危険検知をするために、以下の順序で処理を進める想定。 ① NMEA, AISのセンテンスを時系列データとして扱う ② データ集約層に時系列データを反映する ① デコード処理 ②

    データ更新・反映 ③ データ集約層で集約とデータ処理 ④ 危険検知処理を行う ⑤ ①に戻る 2026年9月11日(金) Go Conference 2026 海上で動くGoサーバー: goroutineとchannelでさばく航行データストリーム 51
  41. Goでの実装に関する考察 -処理順の定義危険検知をするために、以下の順序で処理を進める想定。 ① NMEA, AISのセンテンスを時系列データとして扱う ② データ集約層に時系列データを反映する ① デコード処理 ②

    データ更新・反映 ③ データ集約層で集約とデータ処理 ④ 危険検知処理を行う ⑤ ①に戻る 2026年9月11日(金) 赤字部分は定期実行が必要 (1~3秒間隔) Go Conference 2026 海上で動くGoサーバー: goroutineとchannelでさばく航行データストリーム 52
  42. Goでの実装に関する考察 ※ 青色部分はgoroutineのイメージ サテライトコンパス Serial Source 風向・風速センサ Serial Serial Source

    水深センサ Serial Source 以降、危険検知を 実施する 時系列データ 観測値 (デコード結果) デコード Decoder データ集約層 ・・・ AIS受信機 UDP Source chan 時系列データ 2026年9月11日(金) chan 観測値 Go Conference 2026 海上で動くGoサーバー: goroutineとchannelでさばく航行データストリーム chan 判断結果 53
  43. Goでの実装に関する考察 • ①:NMEA, AISのセンテンスを時系列データとして扱う // 1行のデータ type Line struct {

    Source string // “gps” / “ais” … 機器名 Raw string // “$GPGGA,…” 生のデータ At time.Time // 受信時刻 } func (s *SerialSource) readLoop(ctx context.Context, r io.Reader, out chan<- Line) error { sc := bufio.NewScanner(r) // ...(中略) for sc.Scan() { line := Line{ Source: s.name, Raw: sc.Text(), At: time.Now(), } select { case out <- line: case ← ctx.Done(): // ...(後略) 時系列付きのデータにして goroutine + channel 構成として流していく 2026年9月11日(金) Go Conference 2026 海上で動くGoサーバー: goroutineとchannelでさばく航行データストリーム 54
  44. Goでの実装に関する考察 • ①:NMEA, AISのセンテンスを時系列データとして扱う func (s *SerialSource) readLoop(ctx context.Context, r

    io.Reader, out chan<- Line) error { sc := bufio.NewScanner(r) // ...(中略) case ←ctx.Done(): return ctx.Err() // --- データ集約層・API層(他クライアントからの通信を受ける)を除き 基本的には検知の発生時も意図的に継続動作するようにする。 → 一部センサの故障、数回のエラーだけで危険検知など含めたプログラム全体を止める わけにはいかないため 2026年9月11日(金) Go Conference 2026 海上で動くGoサーバー: goroutineとchannelでさばく航行データストリーム 55
  45. Goでの実装に関する考察 • ②:データ集約層に時系列データを反映する – type ObservationValue interface{ observationValue() } デコード後の型を定義

    // 1つの測定値 type Observation struct { Source string // どの機器から来たか At time.Time // Line.At を引き継ぐ Value ObservationValue // Lineの解釈結果 } 2026年9月11日(金) func (OwnShipPos) observationValue() {} func (OwnShipHeading) observationValue() {} func (Wind) observationValue() {} func (Depth) observationValue() {} func (Target) observationValue() {} // nmeaの各値をObservationValueに持ってる前提 Go Conference 2026 海上で動くGoサーバー: goroutineとchannelでさばく航行データストリーム 56
  46. Goでの実装に関する考察 • ②:データ集約層に時系列データを反映する – デコード処理の実装 – AISのみ2段の処理 • NMEAのデコード ↓

    AISのデコード 2026年9月11日(金) func (d *Decoder) decode(line Line) (ObservationValue, bool) { s, err := d.parser.Parse(line.Raw) // go-nmeaのNMEAデコード // … 中略 ... switch m := s.(type) { // ... NMEA 各種 ...(データの詰替えのみのため省略) case nmea.VDMVDO: return d.ais(m) // NMEA → AISのデコード } return nil, false } func (d *Decoder) ais(m nmea.VDMVDO) (ObservationValue, bool) { pkt, err := d.codec.ParseVDMVDO(&m) // go-aisのAISデコード // ...(後略) Go Conference 2026 57 海上で動くGoサーバー: goroutineとchannelでさばく航行データストリーム
  47. Goでの実装に関する考察 • ②:データ集約層に時系列データを反映する – デコード完了後、 観測値として データ集約層に渡す type Decoder struct

    { parser *nmea.SentenceParser // go-nmeaのパーサー codec *aisnmea.NMEACodec // go-aisのコーデック } func (d *Decoder) Run(ctx context.Context, in <-chan Line, out chan<- Observation) error { // ...(中略)... obs, ok := d.decode(l) // ...(中略)... select { case out <- obs: // ...(後略)... 2026年9月11日(金) Go Conference 2026 海上で動くGoサーバー: goroutineとchannelでさばく航行データストリーム 58
  48. Goでの実装に関する考察 • ③:データ集約層で集約とデータ処理 -goroutine + channnel- func (s *Store) Run(ctx

    context.Context, in <-chan Observation, out chan<- Frame) error { tick := time.NewTicker(time.Second) // 1秒毎のTick発生のため。毎秒で状態取得処理をさせるため defer tick.Stop() // ...中略... case o := <-in: s.apply(o) // ① データの書き込み・更新(次スライドで解説) case now := <-tick.C: s.evict(now) // ② 古い他船を落とす(15分更新がない船舶のデータを消す。なお基準は検討中) snap := s.snapshot(now) // ③ スナップショットとして現在のデータを切り出す(次スライドで解説) var alerts []Alert for _, d := range s.detectors { alerts = append(alerts, d.Detect(snap, now)...) // ④ 危険検知(逐次) } // ...後略... Go Conference 2026 59 2026年9月11日(金) 海上で動くGoサーバー: goroutineとchannelでさばく航行データストリーム
  49. Goでの実装に関する考察 • ③:データ集約層で集約とデータ処理 - apply:状態を進める - func (s *Store) apply(o

    Observation) { // データの更新処理 switch v := o.Value.(type) { case OwnShipPos: s.own.Lat, s.own.Lon = v.Lat, v.Lon s.own.HasPos = v.HasPos s.own.UpdatedAt = o.At // Line.At 由来 case OwnShipHeading: s.own.Heading, s.own.HasHeading = v.Heading, v.HasHeading case Wind: ... case Depth: ... case Target: s.targets[v.MMSI] = v // AISから取得した他船の情報は、MMSI(=ID)をもとに最新の情報に書き換える } } Go Conference 2026 60 2026年9月11日(金) 海上で動くGoサーバー: goroutineとchannelでさばく航行データストリーム
  50. Goでの実装に関する考察 • ③:データ集約層で集約とデータ処理 - apply:状態を進める - func (s *Store) apply(o

    Observation) { // データの更新処理 switch v := o.Value.(type) { case OwnShipPos: s.own.Lat, s.own.Lon = v.Lat, v.Lon s.own.HasPos = v.HasPos // Lat, Lon はそれぞれ 91, 181 の値が入っていた場合はエラーなのでフラグを折る s.own.UpdatedAt = o.At // Line.At 由来 case OwnShipHeading: s.own.Heading, s.own.HasHeading = v.Heading, v.HasHeading // Headingは511の値が入っていた場合エラー } } エラー値が来た場合はこの層で処理する 2026年9月11日(金) Go Conference 2026 海上で動くGoサーバー: goroutineとchannelでさばく航行データストリーム 61
  51. Goでの実装に関する考察 • ③:データ集約層で集約とデータ処理 - snapshot取得 – 現在の状態を切り出す func (s *Store)

    snapshot(now time.Time) Snapshot { targets := make(map[uint32]Target, len(s.targets)) for k, v := range s.targets { targets[k] = v } return Snapshot{At: now, Own: s.own, Targets: targets} } 2026年9月11日(金) Go Conference 2026 海上で動くGoサーバー: goroutineとchannelでさばく航行データストリーム 62
  52. Goでの実装に関する考察 • ③:データ集約層で集約とデータ処理 - snapshot取得 – 現在の状態を切り出す func (s *Store)

    snapshot(now time.Time) Snapshot { targets := make(map[uint32]Target, len(s.targets)) for k, v := range s.targets { targets[k] = v } return Snapshot{At: now, Own: s.own, Targets: targets} } 履歴をDBに保存するならこのタイミングで実施する 2026年9月11日(金) Go Conference 2026 海上で動くGoサーバー: goroutineとchannelでさばく航行データストリーム 63
  53. Goでの実装に関する考察 • ④:危険検知処理を行う(右の例は他船接近検知、純粋関数) – func (d Proximity) Detect(s Snapshot, now

    time.Time) []Alert { 他船接近、 if !s.Own.HasPos { return nil // 比較対象なし } var out []Alert 危険海域接近、 for _, t := range s.Targets { 注意海域侵入 dist := geo.Distance(s.Own.Lat, s.Own.Lon, t.Lat, t.Lon) などあるので検知部分も抽象化。 if dist > 500 { continue // 安全判定 } out = append(out, Alert{ Alert = 危険検知結果。0だと安全。 Kind: "proximity", Severity: "danger", type Detector interface { Reason: fmt.Sprintf("%.0fm < 500m", dist), Detect(snap Snapshot, now time.Time) []Alert RaisedAt: now, } }) } return out – 2026年9月11日(金) Go Conference 2026 海上で動くGoサーバー: goroutineとchannelでさばく航行データストリーム 64
  54. 今後の課題 • 今回は船の上で動かすためのサーバーについて 要件定義 ~ 設計、実装の机上検証まで行った – • NMEAやAISのデコードについては実施済み 今後はサーバーの実装を進めてみて、

    各系統のデータ利用や危険検知処理の動作検証をしてみたい 2026年9月11日(金) Go Conference 2026 海上で動くGoサーバー: goroutineとchannelでさばく航行データストリーム 67