Picoで登山用のGPSロガー作成


# はじめに 
最近山登りを始めたのでGPSロガーが欲しいと思ったのですが、余計な機能付いてて1万円超えちゃうものが多いのでPicoで自作しました。Pythonはあまり使った事がなく、PicoもGPSも初めて扱いました。
なので自分の中ではほぼ無知識の領域です。

# 用意したもの
- Raspberry Pi Pico

https://www.switch-science.com/products/6900?variant=42382131167430

- L76K GPSモジュール

https://www.switch-science.com/products/8729?variant=42531052421318

# 配線接続とデモ用プログラムのインストール
以下の公式Wikiを参考にしました。

https://www.waveshare.com/wiki/L76K_GPS_Module#Working_With_Raspberry_Pi_Pico

こんな感じで仕上がりました。半田を扱う技能は技術家庭で習ったくらいのレベルの腕です。

![twitter_0f6a10429ed8cd85ea8930a5f1610c29.jpeg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/410957/29ab0fcb-3dde-3440-e7b3-367bc02f86fb.jpeg)

いつも悩ましいケース問題ですが、単四エネループの電池ケースがちょうど良かったので使いました。
USBの部分だけ穴を空けてます。(ケースが無ければUVレジンで固めようかと思ってました)

ケーブルは4ピンのものに差し替えて短く切ってます。

https://www.switch-science.com/products/6245?variant=42382097645766

電池駆動を狙ってたのですが、充電電池では容量が少なく日帰りでも入れ替えが必要になるようだったので、5000mAhくらいのモバイルバッテリーを使う事にしました。
USBケーブルの先はモバイルバッテリーです。山では万が一もあるので、ライト付きを使ってます。

https://www.biccamera.com/bc/item/8213143/


# 動かしたプログラム(main.py)
デモのサンプルプログラムを改造しました。デモでは衛星掴んでなくてもprintしてたり、1秒に何回も出力する動きなので、出力条件を変えてCSV形式でファイル出力させました。
改変で追加した部分は、以下くらいです。
- Pico内にCSVファイル作って保存
- たまに出るエラー停止の回避
- 衛星掴んでから5秒おきに記録(重複した秒の記録は取らない)
- 日付をUTC(ISO 8601)にしました

CSVファイルには以下のデータが出力されます。
<table>
<tr>
<th>日時</th><th>緯度</th><th>経度</th><th>高度</th><th>ジオイドの高さ</th><th>GPSの状態</th><th>HDOP値</th><th>使用中の衛星の数</th>
</tr>
</table>

```python:main.py
import time
from pico import l76x
from pico.micropyGPS.micropyGPS import MicropyGPS

gnss_l76b=l76x.L76X(uartx=0,_baudrate = 9600)
gnss_l76b.l76x_exit_backup_mode()
gnss_l76b.l76x_send_command(gnss_l76b.SET_SYNC_PPS_NMEA_ON)
parser = MicropyGPS(location_formatting='dd')
sec = 0
tpfile = open("gpslog.csv","a")

while True:
    if gnss_l76b.uart_any():
        try:
            sentence = parser.update(chr(gnss_l76b.uart_receive_byte()[0]))
        except:
            pass
        if sentence and parser.satellites_in_use > 0 and sec != parser.timestamp[2] and (parser.timestamp[2] % 5) == 0:
            datetime = f"20{parser.date[2]:02}-{parser.date[1]:02}-{parser.date[0]:02}"
            datetime += f"T{parser.timestamp[0]:02}:{parser.timestamp[1]:02}:{parser.timestamp[2]:02}Z"
            lat = f"{parser.latitude[0]:.9f}"
            lon = f"{parser.longitude[0]:.9f}"
            alt = int(parser.altitude)
            data = f"{datetime},{lat},{lon},{alt},{parser.geoid_height},{parser.fix_stat},{parser.hdop},{parser.satellites_in_use}\n"
            print(data)
            sec = parser.timestamp[2]
            tpfile.write(data)
            tpfile.flush()
```

# ChatGPTさんのプログラム解説

プログラムをChatGPT(GPT-4)に読ませた結果です。

>
>このPythonスクリプトは、Raspberry Pi PicoとL76X GPSモジュール（Pico-GPS-L76Bボードに搭載）を使用して、GPSデータを収集し、それをCSV形式で保存するものです。このスクリプトの機能は次のとおりです：
>
>1. L76X GPSモジュールを初期化します。
>2. GPSモジュールからNMEA0183形式のデータを受け取り、MicropyGPSオブジェクト（parser）を使用して解析します。
>3. GPSモジュールが利用可能な衛星データを持つ場合、そして前回のログから5秒経過した場合、GPSデータをCSV形式で保存します。データには日時、緯度、経度、高度、ジオイドの高さ、GPSの状態（フィックス状態）、HDOP値（Horizontal Dilution of Precision, 水平精度低下）、使用中の衛星の数が含まれます。
>
>このスクリプトは無限ループで動作し、新しいGPSデータが利用可能になるたびにそれを解析し、ログとして保存します。ログは"gpslog.csv"という名前のファイルに保存され、新しいデータはファイルの末尾に追加されます。
>
>このスクリプトを使用する際には、picoとpico.micropyGPS.micropyGPSというライブラリが必要で、これらはRaspberry Pi Pico向けの特定のライブラリである可能性があります。
>
>以上がこのスクリプトの主な機能と動作の概要です。特定の部分について詳細な説明が必要な場合は、お知らせください。

なかなか正しく解釈してくださってます。

ただ、作った本人は「データクレンジングに使えるかな？」と思ってあんまり意味がわからず出力している「ジオイドの高さ」について聞いてみました。

>「ジオイドの高さ」または「ジオイド高」とは、地球の海面相当面（ジオイド）と楕円体面（地球の理想的な形状を表す数学的な形状）との差を表す高さのことを指します。
>
>地球の表面は完全な球形ではなく、また、理想的な楕円体（回転楕円体）でもありません。地球の実際の形状は、海洋、山地、地下の密度の変動などにより、これらの単純な形状からずれています。このため、地球の「真の」形状を表すためには、ジオイドという概念が使用されます。ジオイドは重力が等しい場所を結んだ面で、海水が自由に流れるとこの形状を取るとされています。
>
>一方、GPSなどの衛星ベースの測位システムは、地球を楕円体として扱います。これにより、計算が大幅に簡略化されますが、その結果、楕円体とジオイドとの間にはずれ（ジオイド高）が生じます。
>
>GPSデバイスは通常、楕円体に対する高度（楕円体高）を提供しますが、多くの実用的な用途（例えば、海面高度の測定など）ではジオイド高が必要となります。そのため、GPSデバイスはしばしばジオイドモデルを内蔵しており、これを使用して楕円体高からジオイド高への補正を行います。

なるほど、よくわかりません。説明されても理解できるかはまた別の問題ですね。
「HDOP値」についても聞いてみました。

>HDOP値は、GPSシステムが利用している衛星の相対的な位置に基づいて計算されます。衛星が空の広い範囲に分散している場合、HDOP値は低くなり、位置情報の精度は高まります。一方、衛星が空の一部に集中している場合、HDOP値は高くなり、位置情報の精度は低下します。
>
>HDOP値は次のように解釈されます：
>
>1以下: 最高精度
>1 - 2: 優れた精度
>2 - 5: 良好
>5 - 10: 平均
>10 - 20: 低精度
>20以上: 非常に低精度
>したがって、低いHDOP値が、より信頼性の高いGPSデータを示しています。

なるほど。数値が低いといいみたいです。わかりやすい。
「衛星の数」についても聞いてみました。

>GPS受信機は、地球を周回する複数のGPS衛星から信号を受信します。それぞれの衛星からの信号にはその衛星の現在位置と、その信号が送信された時間が含まれています。これらの情報を用いて、受信機は自身の正確な位置を三角測量によって計算します。
>
>GPSの位置決定には少なくとも4つの衛星からの信号が必要です。それぞれの衛星が提供する情報には誤差が含まれているため、複数の衛星からの信号を使うことでその誤差を相殺し、より精度の高い位置情報を得ることができます。したがって、satellites_in_useが大きいほど、位置情報の精度が高くなる可能性があります。
>
>ただし、これはあくまで理論的な話で、実際の精度は多くの要因によって影響を受けます。例えば、衛星の配置、大気の状態、建物や樹木による遮蔽などが影響を及ぼすことがあります。

衛星数とHDOPでなんとか閾値を設ければ、大きく位置ズレしたデータを除去できそうですね。

# データを使ってみた
一家に一台はあると噂されているElasticsearchにインポートしてKibanaで可視化してみました。
![スクリーンショット 2023-05-12 19.17.48.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/410957/75beeaa1-e2cf-6635-2e29-0fc07ce6c40b.png)

一番やりたかった事ですが、kml形式にしてGoogle Earthで可視化させて登山動画の冒頭で使いました。

https://youtu.be/z7ya2Ochhlc?t=0

https://youtu.be/fCo4dFS3lLw?t=0

kmlは調べても簡単に理解できない形式でしたが、以下のようにサンプル内の設定やデータを入れ替えて使ってます。
```kml:本栖湖から竜ヶ岳.kml
<?xml version="1.0" encoding="UTF-8"?>
<kml xmlns="http://www.opengis.net/kml/2.2" xmlns:gx="http://www.google.com/kml/ext/2.2" xmlns:kml="http://www.opengis.net/kml/2.2" xmlns:atom="http://www.w3.org/2005/Atom">
<Document>
	<name>Paths</name>
	<description>Examples of paths. Note that the tessellate tag is by default
 set to 0. If you want to create tessellated lines, they must be authored
 (or edited) directly in KML.</description>
	<Style id="yellowLineGreenPoly">
		<LineStyle>
			<color>7f00ffff</color>
			<width>4</width>
		</LineStyle>
		<PolyStyle>
			<color>7f00ff00</color>
		</PolyStyle>
	</Style>
	<StyleMap id="yellowLineGreenPoly0">
		<Pair>
			<key>normal</key>
			<styleUrl>#yellowLineGreenPoly1</styleUrl>
		</Pair>
		<Pair>
			<key>highlight</key>
			<styleUrl>#yellowLineGreenPoly</styleUrl>
		</Pair>
	</StyleMap>
	<Style id="yellowLineGreenPoly1">
		<LineStyle>
			<color>7f00ffff</color>
			<width>4</width>
		</LineStyle>
		<PolyStyle>
			<color>7f00ff00</color>
		</PolyStyle>
	</Style>
	<Placemark>
		<name>Absolute Extruded</name>
		<description>Transparent green wall with yellow outlines</description>
		<styleUrl>#yellowLineGreenPoly0</styleUrl>
		<LineString>
			<extrude>1</extrude>
			<tessellate>1</tessellate>
			<coordinates>
138.601242065,35.454395294,929
138.601242065,35.454368591,929
138.601242065,35.454357147,929
:
138.583694458,35.446601868,1477
138.583694458,35.446601868,1477
138.583694458,35.446601868,1477
			</coordinates>
		</LineString>
	</Placemark>
</Document>
</kml>

```


大変満足しました。