Tag Archives: ROVER

RTK基準局を設置

Ublox ZED-F9P受信機を入手したので前から気になっていたRTKをやってみます。
最終的には庭で自動運転車を走らせて芝生を自動的に刈り込ませるのが目標です。

RTK

RTKというのはReal time kinematicの略でGPS(GNSS)衛星からの信号を高精度に解析してセンチメートル単位の測位を可能にする技術です。
※GNSSは測位システム全般を指す言葉でその内の一つがGPSですが、ここでは全てGPSと呼ぶ事にします。

センチメートル級測位というと日本の衛星「みちびき」のCLAS等もありますが、みちびきはまだ7機の完全体制になっていないし、精度的にもRTKの方が良い(らしい)のです。

RTKではGPS受信機を2つ使います。一方はアンテナの位置が既知である基準局で、衛星からの電波を受けて、その誤差を補正データとして出力します。もう一方は実際に測定する側(今回だと自動運転車、以下ROVERと呼びます)の受信機で、自分が受信した信号を基準局のデータで補正して自己位置を推定します。

GPSの誤差は近い距離内にある受信機間だとほぼ同じになるので基準局が近くにあるとROVERの位置が精度良く測定できる事になります。

基準局は必ずしも自分で用意する必要はなく、こんなところや、こんなところを見ると日本各地に基準データーを公開して頂けているサイトがあり、近い場所にあればそちらを利用するのが手っ取り早いです。
でも折角なので今回は自宅に基準局を設置して庭で測位してみようと思います。

受信機

入手した受信機はジオセンス社のF9PX1という機種で、Ublox社のZED-F9Pモジュールを搭載した基板です。
基板のままだと不安なので写真のケースを3Dプリントしました。
このケースの3DデータはThingiverseにアップしました

アンテナ

アンテナもUblox社製のANN-MB-00です。
基準局のアンテナは一旦設置したらあまり場所を動かしたくありません。cm単位で測位するための基準点なので1cm足りとも動かしたくないのです。 なので有り合わせの鉄板とアングルを溶接してアンテナ基台を作り、屋根に固定しました。
ここは屋根の一番高い所ではないので空を遮ってしまう部分が結構あります。本当はもっと高い位置に取り付けたいのですが、まずはこれでやってみます。

基準局のアンテナ位置を調べる。

基準局はアンテナの位置(座標)が正確に分かっている必要があります。
座標を知るには色々と方法がある様ですが、一番現実的な方法として暫く受信したデーターをファイルに書き出し、これを国土地理院の電子基準点のデーターから補正する方式でやってみます。
電子基準点のデーターは無料だとリアルタイムには提供されず、一時間毎に更新されるデーターをダウンロードする事になります。よって受信データーも一旦ファイルに落としておき、後付けで解析するのです。

手順としては以下の様に実施しました。
・ZED-F9P受信機を設定する。
・暫くの間データーを受信し、結果をファイルに落とす。
電子基準点のサイトから補正データーをダウンロードする。
rtklibに含まれる後付け解析ツールrtkpostを使って測位結果を算出する。
・測位結果はある程度ばらつきがあるので、その平均値を基準局の座標とする。

ZED-F9P受信機の設定

最初にZED-F9Pを設定します。
参考にしたのは下記書籍及びサイトです・・・

・トラ技2019年10月号 F9Pの設定詳細・RTKLIB・専用基準局製作
ZED-F9Pの設定方法
高精度衛星測位 RTK-GNSSチュートリアル

u-center(u-bloxの設定ツール)を開き、まずは上記資料の中で今回の座標特定に必要な内容だけを変更しました。

デフォルト設定値から変更した内容は・・・
・BaudRateを230400に設定
・受信衛星の選択画面(Viewメニューの「Generation 9 Advenced Configuration View」)を開いたが特に変更する箇所が見当たらなかったのでデフォルトのまま。
・ViewメニューのMessage Viewを開く。
・UBX-CFG-NAV5のMin SV Elevationをとりあえず15°程度に設定。
・UBX-CFG-RXMの中でRAWX,SFRBXを出力。
・NMEAではGxGGA,GxRMC,GxGSVを残してあとは停止する。
・UBX-CFG-NMEA内で、「High precision mode」にチェックを入れる。
・現段階ではRTCM出力は不要。
・変更を書込む。

なおデーターの更新レートを標準の1秒間隔から0.2秒間隔に変更できる様な事が色々なサイトに書いてありますが、私の所では0.2秒間隔だと受信中にRAWXデータが止まってしまうという現象が発生しました。色々試すと約0.7秒より長くすると安定したので、今回は1秒間隔(デフォルト通り)で使用しました。
(当面の用途ではRAWXはアンテナ座標を調べるとき以外は出力しなくて良いので大きな問題はない筈です)。

RTK-LIB

RTKを弄るにはrtklibをインストールしておくのが定番です。
私の環境ではrtklibのサイトを開くとブラウザから「この接続ではプライバシーが保護されません」というメッセージが出ますが、気にせず先に進みます。
そしてWindows版の最新2.4.3b34(ベータ版ですが)をダウンロードしてzipを解凍します(特にインストーラー等はなく解凍するだけ)。

まずはRTK-Naviで受信してみる。

その前にF9P基板とPCをUSBケーブルで接続し、デバイスマネージャーでCOM番号を調べておきます。

そしてRTK-LIBを解凍した階層下のbinディレクトリにある、rtknavi.exeを実行するとこんな画面が開きます。

右上の「I」というボタンを押すと次のダイアログが開きます(「I」は入力設定用ボタン)。

(1)Roverにチェックを入れ、TypeはSerialに設定してCmd列の「…」ボタンを押すと下のダイアログが出るので、PortのところにCOM番号、Bitrateのところに先程設定した230400bpsをセットして「OK」を押します。

そしてFormat列をu-blox UBXに設定してOKを押すとメイン画面に戻ります。

メイン画面の下の方の「Options…」を押すと次のダイアログが開くので、GPS,GLONASS,GAlileo,QZSSにチェックをいれて「OK」をクリック。

この時点で試しに受信してみましょう。
メイン画面左下の「▶Start」を押すと受信データがグラフで表示されます。

こんな感じで受信出来ていたら「■Stop」を押して一旦止めます。

受信ログを取得する。

ではログを取得する準備をします。
メイン画面右上の「L」を押してログ設定のダイアログを開きます。
(6)Roverにチェックを入れ、TypeにFileを設定し、LogFilePathsに適当なファイル名を設定します。
(ログファイル名には日付や時刻を含めておくと後で分かりやすいです。拡張子は.ubxにしておきます。)。

OKを押してMAIN画面で「▶Start」を押すと受信が始まるので、この状態でしばらく放置します(私は半日程置きました。長い方が良い筈ですが、どれくらいが適当かは不明。なお最初は短い時間で一度試してからが良いと思います。)。

取ったデータを解析する

データを解析するには上で落としたログをRINEX形式のファイルに変換します。
これにはRTKLIBに付属のRTKCONV.exeを実行します。
先頭の「RTCM,RCV RAW or RINEX OBX ?」に先程落としたログファイル名を指定すると出力ファイル名は自動的に設定されます。

「Options…」を押して出力形式が「RINEX Ver3.03」になっている事を確認します(違っていたら設定する)。

「OK」でMAIN画面に戻って「▶Convert」を押すと変換が始まり、XXX.obs(これがRINEX形式のファイル)が出来ていればOKです。

電子基準点からデータを取る。

受信したデータを解析する為に電子基準点の補正データを取得します。
まず下記にアクセスして・・・
https://terras.gsi.go.jp/
「観測データ取得」をクリック。なにやら「同意するか?」と聞かれたら「同意」をクリック・・

するとこんな地図が現れます。
各地の電子基準点で埋め尽くされていますね。。。

地図を拡大して最も近い基準点を選択し・・・

「ダウンロード」をクリック・・・

下の様に開始日時、終了日時には上記受信ログを落とした時間帯を含む範囲を指定します。
そして衛星はGRJE、RINEXverは3.02を指定。
「任意時間のデータダウンロード」をクリック。

するとこんな画面が開くので、「観測ファイル」、「衛星軌道情報ファイル」それぞれダウンロードします。

これでxxxx.o.gz , xxxx.tar.gzの2つのファイルがダウンロード出来ている筈です。
これらを解凍してxxxx.n,xxxx,g,xxxx,q,xxxx.l等に戻します。
(*.gzやtar.gzはUNIXでは一般的な圧縮/アーカイブ形式です。解凍方法が分からない場合は検索すると沢山出てきます。なおWindowsでも10あたりからtarコマンドが入っている様です。)
なお最近発見したのですが、xxxx.o.gzは解凍しなくても、そのまま指定すればRTKPOSTが読んで貰える様です。
またxxxx.tar.gzをそのまま指定するとエラーは出るものの解凍だけしてくれる様です。
 なので一旦エラー覚悟で実行して解凍されたファイルを改めて指定すればtarコマンドを実行せずに処理できます(今イチ良く分からん動作ですが)。

※素のままのファイル名だと後で分かりにくいので時刻範囲が分かるファイル名に変更しておいた方が良いと思います。

RTKPOSTで解析する。

以上により一通りのデータが揃ったので解析します。これにより自分のアンテナ位置の正確な位置(座標)を得るのです。

ではRTKLIBに付属のrtkpost.exeを実行すると下記の画面が開きます・・・


下の方の「Options…」ボタンを押して下記ダイアログを開き、Setting1タグにて・・・
・Positioning Mode をKinematicに。
・Filter Typeは一番厳密そうなCombinedを選び・・・。
・Elevation Mask(°) を30°程度に設定し(この辺りは適当)・・・。
・GPS,GLONASS,Galileo,QZSSにチェックを入れます。


PositionsタブのBaseStationをRINEX Header Positionに設定します。
その他にも細かなパラメーターが色々あって何がベストか分からないのもありますが、このあたりでOKを押してMAIN画面に戻ります。

後は処理するファイルを下記の様に設定していきます。
・RINEX OBS: Rover欄にはRTKNaviでダウンロードしてRTKConvで変換したRINEXファイル(XXX.obs)を指定。
・RINEX OBS: Base Stationには電子基準点からダウンロードして解凍したXXX.oファイルを指定。
・RINEX NAV/CLK,SP3,FCB,IONEX,SBS/EMS or RTCM欄にはXXX.n , XXX.g , XXX.l , XXX.qを指定。
・そして一番下の欄には出力ファイル名が自動的に入っているので・・・
「▶Execute」を押すと解析が始まります。

データー期間が長いと結構時間を要します。
終わったらPlotボタンを押すとこんな感じで分布が見えるはずです。

緑の点がRTKがFixしている(Cm級測位が出来ている)箇所で、黄色の点はFixしておらず精度が悪い点です。

取れたデータからアンテナ座標を求める。

アンテナ座標を求めるにはFixしているデーターだけ使いたいのでxxx.posファイルを以下の様にちょいといじります。

まずxxx.posファイルをテキストエディタで開くと下の様になっています。
この中でQと表示された列が1である行はFIXしたポイントで、その他の2や5になっている個所は精度が悪いのポイントなので、1の行を残して取り除きます。

これはテキストエディタで作業しても良いですが、面倒なのでこのページ末に記載したrubyスクリプトで処理しました。

Q=1の行以外を消してプロットし直すと次の様になりました。


同じスクリプトに-cオプションを指定して実行するとCSVファイルが出ます。
これをExcel等の表計算ソフトで開き、latitude,lognitude,height列を全部平均した座標をアンテナの位置としました(実はExcelは値段が高く持っていないのでLibreOfficeで実行した・・・)。
ついでに標準偏差を出してみるとLatitude≒9.2E-8, Lognitude≒1.67E-7, Hight≒35.07cmとなりました。 3σをmmになおすと30.9mm, 47.0mm, 105.2mmなので、まあセンチメートル単位で測位出来ていると思います。

そして・・・

以上でアンテナ座標を決める事が出来たので引き続き以下の作業を実施していきますが、長くなったので続きは後日投稿します。

・RaspberrypiでF9P受信器に接続してRTCM3データを取得し、同時にNTRIPcasterを実行して自宅内に配信。
・もう一つのF9P受信機をPixhawkに接続してRTK測位。
・ローバーを作成して庭で走らせる。

データー処理スクリプト

#!/usr/bin/env ruby
# coding: utf-8
#
# 2025.12.07 by https://www.hoihoido.com (konchi@hoihoido.com)
#
# rtkpost.exeが吐き出す*.posファイルからFIXしている行を取り出す。
# ※FIXしたらQ列(左から5ブロック目)が1になる。
#
# 引数:
# -c CSV出力(ヘッダなし)
# -q num Q列がnumで示す値である行だけ抜き出す。
# ※Q列はFIXやFLOAT等の状態を示す。1がデフォルトでFIX状態。
#

require 'optparse'

options = {}
opt = OptionParser.new
opt.on("-c", "--CSV", "CSV output") { |v| options[:CSV] = v }
opt.on("-q VAL", "--Q", Integer, "Q num (1=FIX:default)") { |v| options[:Q] = v }

opt.parse!(ARGV)

if ( options[:Q]) then
qval=options[:Q].to_s
else
qval="1"
end

n=0
while (l=gets()) do
if (options[:CSV] and l =~ /^% +GPST +/) then
title=l.split
print "DATA,"
print title[1..-1].join(','),"\n"
end

if l =~ /^\d{4}\/\d{2}\/\d{2} +\S+ +\S+ +\S+ +\S+ +(\d) / then
if $1 == qval then
if ( options[:CSV]) then
l.gsub!(/ +/,",")
end
puts l
n+=1
end
else
if (! options[:CSV]) then
puts l
end
end

end

print(",AVE,")
print("=average(C2:C",n+1,"),")
print("=average(D2:D",n+1,"),")
print("=average(E2:E",n+1,")\n")
print(",σ,")
print("=stdev(C2:C",n+1,"),")
print("=stdev(D2:D",n+1,"),")
print("=stdev(E2:E",n+1,")\n")
print(",3σ,")
print("=3*C",n+3,",")
print("=3*D",n+3,",")
print("=3*E",n+3,"\n")
print(",3σ[mm],")
print("=40000000/360*C",n+4,"*1000,")
print("=40000000/360*D",n+4,"*1000*cos(C",n+2,"/180*pi()),")
print("=E",n+4,"*1000\n")