中国で一番おもしろい場所

中国で一番おもしろい場所は間違いなく食料品店です。

中国の食料品店は日本製と欧州製の商品に溢れており、見知った商品が脈略もなく陳列されている点がおもしろいだけでなく、中国人の好みがよく反映されている点が興味深くもあります。

日本人でも知らない人のほうが多いだろう網走ビールの真横に沖縄パイン酒とヨーグルッペと謎の乳酸菌飲料が並べられているのが中国の食料品店です。

そのすぐ手前の列には、サンガリアとドイツの ERDINGER と青島ビールが一緒に並んでいたりします。選別基準も良く分かりませんが、陳列方法はもっと訳が分かりません。

ただ、全体をよく見回していくと、中国人が好きな食品の傾向が分かります。




輸入されているのは飲料だけではなく、レトルトコーナーにはイタリアのスパゲッティソースと一緒に出前一丁が大きなスペースを占有していて、中国企業の製品が端の方に置かれていますし、チョコレート以外の菓子類は日本製と韓国製ばかりです。それも見たことも聞いたこともない無名のものと有名どころが半々ぐらい。

残ったチョコレートも高価なものは、世界中のどこにでもあるスイス製やドイツ製で、中国製のものはお土産用のパッケージに包まれたものが少量あるぐらいです。

日本製がほとんど見当たらないのはヨーグルトなどの乳製品コーナーで、こちらはドイツ製とスイス製とオーストラリア製で埋められています。

この写真だけを見ていると、どこの国に来たのか全く分からなくなりそうですが、空港からドイツ車で送迎されて、毎日、フランス人やドイツ人と顔を合わせて、オージービーフのハンバーガーばかり食べていると、本当にどこにいるのか分からなくなります。

中国人には申し訳ないですが、中華料理は日本や米国のレストランの方が美味しいぐらいで、香港や台湾と違って本場の美味しい中華料理を食べたいという気持ちにはならないです。

日本には高級中華料理店こそあっても、中国企業が作った飲料や調味料などに誰も興味を示さないのに、中国のスーパーマーケットは日本語パッケージそのままの輸入品で溢れているところにも、現在の両国の関係性が反映されているような気がしてきます。

日本製と同じぐらいか、それ以上に欧州製品の存在感があるのも、また示唆的です。

中国の食料品店は本当におもしろいです。

まあ、言い換えると他に見どころが乏しいということでもあるのですが。

太陽光パネルの発電を計測する電子工作

家庭用の市販の太陽光パネルは違いがよく分かりません。

よく晴れた日には1日でスマートフォンを満充電にできるなど、それなりに実用性がありそうな話は耳にしますが、発電量が気象条件に依存するだけに実際のところはよく分かりません。

よく分からないものは、自分で調べてみるしかありません。

発電量を調べるのは割と簡単でして Hall effect sensor というものを用意すれば、個人でも測定できます。


30Aレンジ電流センサモジュール ACS712モジュール

このセンサモジュールを制御するための Microcontroller (MCU) と接続用のケーブル、そしてプログラミング用のパソコンさえあれば測定を始められます。

センサモジュールはセンサの計測範囲によって3種類に分類されますので、お使いの太陽光パネルに応じて適切な計測範囲のものをお求めください。

略称 計測範囲 センサ型番
5A ±5A ACS712ELCTR-05B-T
20A ±20A ACS712ELCTR-20A-T
30A ±30A ACS712ELCTR-30A-T

ここでは 5A (ACS712ELCTR-05B-T) を用いるものとして話を進めます。

※ 電子工作では電圧や電流の向きなどを間違えるとコンデンサなどが爆発したり、回路が発火したりすることもありますので、部品を揃える段階から細心の注意を払ってください




MCU もお好きなものを自由に使っていただいて構いません。

「購入するまでもなく自宅にいっぱい転がっているから」という理由から、私は Arduino Uno を使用します。

センサモジュールと MCU の準備ができましたら、やることはたったの3つだけです。

(1)センサモジュール制御用のプログラムを書いて MCU にアップロードします。

つぎに(2)MCU とセンサモジュールをジャンパワイヤを用いて接続します。

最後に(3)データ受信用のプログラムを書いてパソコンを待機させます。

ここまでできましたら、あとは太陽光パネルとセンサモジュール、MCU とパソコンをそれぞれ接続させて実際に計測を行えます。




(1)センサモジュール制御用のプログラムを書く

ここでは開発環境として Arduino Software (IDE) 1.8.10 Linux 64 bits を使用しています。

Arduino を初めて使用される方は 公式ページ から、お使いの環境に適した最新の Arduino Software (IDE) をダウンロードしてインストールしておいてください。

センサモジュールの制御と USB の出力には、最低限、これだけあれば動きます。SD カードへの出力など、必要に応じて適宜改編してください。

const int analogInput = A0;

const double vcc = 5; // 5V
const double quiescentVoltage = 0.5 * vcc;
const double sensitivity = 0.185; // 185mV

void setup() {
  Serial.begin(9600); //baud rate 9600bps
}

void loop() {
  double rawVoltageInput = (double) analogRead(analogInput) / 1023 * 5.0;
  double volts = rawVoltageInput - quiescentVoltage;
  double amps = volts / sensitivity;

  Serial.println(String(volts, 3) + "\t" + String(amps, 3));
  delay(500);
}

これを保存して MCU にアップロードするとセンサモジュールの出力を受け取って、USB経由でパソコンにデータを書き出せるようになります。

何をしているのかを一言でまとめると、センサがアナログの入力を読み取って 10bit の(0 から 1023 までの)数値に変換するので、その数値を理解できる単位に変えて画面に出力するという処理を書いています。

なんだか得体の知れない数値 sensitivity = 0.185 は製造元のデータシートから得られます。

https://www.allegromicro.com/~/media/files/datasheets/acs712-datasheet.ashx [PDF]

ついでにデータシートからは ±5 A で気温 25°C のとき、センサの固有誤差は ±1.5% で精度階級は 1.5 ということが分かります。

なお Arduino の操作や用語については、日本語の参考書もありますので分かりにくかったら参考にしてください。


みんなのArduino入門

(2)MCU とセンサモジュールを接続する

ソフトウェアの準備ができましたら、次はハードウェアの準備です。

センサモジュールには VCC OUT GND という端子があります。それぞれ、電源端子、出力端子、グランド端子という意味があります。

検索すると大学の講義資料などで詳しく解説されていますので、興味がありましたら調べてみて下さい。

計測するために必要なことは、それぞれの端子を識別して、正しく接続することです。

具体的には以下のようになります。

プログラムの方で analogInput に A0 を指定して読み込んでいるので A0 に繋いでます。

接続に使っているのはジャンパワイヤという部品です。電子工作の基本部品の一つで、よく使われるので通常はまとめ買いされます。


ELEGOO 多色デュポンワイヤ 120pcs

(3)データ受信用のプログラムを書く

ソフトウェアとハードウェアの準備ができましたら、あとは実際に計測をおこなうだけです。

ただし、現状のままでは MCU はセンサモジュールの出力を無限に垂れ流すだけなので、どこかに(SDカードなどに)出力を書き出すか、USB から出力を受け取らないとデータを利用することができません。

SDカードソケットモジュールなどを利用すると外部メディアも利用できますが、そのモジュールを私が持っていないので USB で接続したパソコンに出力することにします。

出力されたデータを受け取るプログラムは Python でも Ruby でも、お好きな言語で実装していただいて問題ありません。

今回は JavaScript (node.js) を使って手抜きします。

この場合は適当なプロジェクト(以下の例では currentMeasurer)を作成して、serialport をインポートする準備をお願いします。

$ mkdir currentMeasurer && cd currentMeasurer
$ npm init
$ npm i serialport
$ touch measurer.js
$ vim measurer.js # ファイル編集

編集するファイルの中身は以下のようになります。標準出力 (stdout) している部分は直接ファイルに書き出してしまう形式に書き換えてしまっても構いません。

const SerialPort = require('serialport');
const Readline = require('@serialport/parser-readline');
const port = new SerialPort('/dev/ttyACM0', {baudRate: 9600,});
const parser = port.pipe(new Readline());

parser.on('data', line => console.log(`${line}`));

また、上の3行目で /dev/ttyACM0 と決め打ちしているところがありますが、これはシリアルポートなので、お使いの環境ではこのまま実行できない可能性があります。

以下の方法で MCU が使用しているシリアルポートを特定して、該当部分を必要に応じて書き換えてください。

Linux

$ ls /dev/tty*

ターミナルから上のコマンドを打つと ttyUSB もしくは ttyACM で始まるシリアルポートが見つかります

Mac

$ ls /dev/tty*

ターミナルから上のコマンドを打つと tty.usbmodem もしくは tty.usbserial で始まるシリアルポートが見つかります

Windows
デバイスマネージャーを立ち上げて ポート(COMとLPT という項目をクリックすると表示されます

準備ができましたら、USB ポートに MCU を接続した状態でプログラムを実行してください。

$ node measurer.js > log.txt 

計測

この状態のまま、センサモジュールと太陽光パネルを接続すると計測が始まります。

私が購入した太陽光パネルの出力は 2.1mm DC プラグになっていましたので、それに合うアリゲータクリップスを用意しました。

このアリゲータクリップスを介してセンサモジュールと太陽光パネルを接続します。


uxcell DCパワージャックアダプター 2.1mm x 5.5 mm

そしてセンサモジュールから得られた出力を MCU 内でボルトとアンペアに変換して USB ケーブルを通して PC に出力します。

出力された数値を PC で受け取ってテキストファイルに記録していきます。

うまく行けば log.txt というファイルの中にボルトとアンペアの数値が並んでいくはずです。

プログラム上は止めないと無限にループするようになっているので、計測を終えるときに停止させてください。

これで実際に出力された数値が出せればよかったのですが、いつまで経っても晴天にならないうちに某国に出張して、また自宅に帰れない生活が始まってしまったので、一度、ここで区切ります。

Garmin / Strava の走行記録をまとめて表示する

いままでに走ったことのある場所を一つの地図上にまとめて表示したい。自分と誰かの走行記録を一緒に表示したい。まだ走ったことのない場所を手早く見つけたい。

Garmin Edge といったサイクルコンピュータや Strava のような SNS を利用されている場合、今までのライドログを複数重ねて地図上にマッピングすることは簡単に実行できます。

必要な手順は大まかに3つぐらいです。

(1)座標データを集める
(2)座標データを Leaflet で描画する
(3)描画された地図(HTMLファイル)を表示する

一つづつ見ていきましょう。

(1)座標データを集める

Garmin デバイスの場合、スタートボタンを押して計測を開始してから保存を完了するまでのライド記録のすべてを FIT という形式のファイルに保存しています。

1つのファイルにライド1回分の情報があります。

この FIT ファイルは連携している Strava からもダウンロードすることが可能です。

この中には移動速度や気温やケイデンスなどのあらゆる計測データが記録されていますので、そこから座標データを抜き出して地図上に描画してやれば目的が達成されます。

ところが、ここで少し困ったことには FIT ファイルはバイナリファイルなので、テキストエディタなどで中身を覗こうとしても普通の人には読めません。


FITファイルの概要とFIT SDKのサンプルコード
https://qiita.com/harry0000/items/5931bbbf3d5b68fe8bc0


そこで一般的にはプログラムを使ってファイルから必要な情報を抜き出す処理を行います。




こうしたプログラムにはさまざまな種類がありまして、何を使ってもいいのですけれども、開発元からも公式にプログラムが配布されていますので、ひとまずはそれを使用することを考えます。


Downloads – THIS IS ANT
https://www.thisisant.com/developer/resources/downloads/


上のリンク先から FIT SDK という項目を選択すると Please Accept this Agreement Before Viewing This File という条件文が表示されますので、それを一読したあとに同意されるとファイルのダウンロードが行われます。

ダウンロードされた ZIP ファイルを解答すると java というディレクトリ(フォルダ)がありますので、その中にある FitCSVTool.jar というファイルが存在することを確認してください。

これは Java という言語で書かれたプログラムファイルです。プログラムを実行するには Java Runtime Environment という外部ソフトウェアが必要になります。

このプログラムファイルに正しく引数を与えて実行すると FIT ファイルがテキストファイルに変換されます。具体的にはこうやります。

$ java -jar FitCSVTool.jar -b activities/0000000000.fit outputFileName.csv

作成されたテキストファイルの中身には、以下のような記録が数十行から数千行に渡って並んでいます。

Data,12,record,timestamp,”933504317″,s,position_lat,”425645280″,semicircles,position_long,”1667343683″,semicircles,distance,”4978.48″,m,altitude,”56.60000000000002″,m,speed,”8.491″,m/s,unknown,”2710″,,unknown,”706″,,temperature,”27″,C,enhanced_altitude,”56.60000000000002″,m,enhanced_speed,”8.491″,m/s,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,

やるべきことは、ここから位置座標データを抜き取ることです。

目的の座標データは position_lat と position_long の直後に並んでいる数字列(太文字で強調している部分)です。

見た目は謎の数字列でしかありませんが、これを 2^31 (2の31乗)で割ってから 180 を掛けあわせると緯度と経度になります。

つまり、上の座標 (425645280,1667343683) とは 35°40’37.8″N 139°45’18.5″E のことです。

記憶容量の節約やプログラム処理の容易さなどの理由から32ビット整数で緯度経度を表現したら、こうなったのでしょうね。

対象となるファイルと目的が定まりましたら、対象の部分を抜き取って緯度経度に変換するという処理をスクリプトに落とし込み、作業をループで回して個々のファイルを処理していけば、必要な座標データは揃います。

一例をあげると、こんな感じに書けばやりたいことはできるかなと。

import com.garmin.fit.*;
import com.garmin.fit.csv.MesgCSVWriter;
import com.garmin.fit.csv.MesgFilter;

import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.List;
import java.util.stream.Collectors;

public class Converter {

	public void convert(String dirPath) {
		try {
			for (File file : listFiles(dirPath)) {
				String outputFileName = file.getName().substring(0, 8);
				
				System.out.println(outputFileName);
				
				Decode decode = new Decode();
				MesgCSVWriter mesgWriter = new MesgCSVWriter(outputFileName + ".csv");
				FileInputStream fileInputStream = new FileInputStream(file);

				MesgFilter mesgFilter = new MesgFilter();
				mesgFilter.addListener((MesgDefinitionListener) mesgWriter);
				mesgFilter.addListener((MesgListener) mesgWriter);

				decode.addListener((MesgDefinitionListener) mesgFilter);
				decode.addListener((MesgListener) mesgFilter);
	
				while (fileInputStream.available() > 0) {
					decode.read((InputStream) fileInputStream);
					decode.nextFile();
				}
				mesgWriter.close();
			}
		} catch (IOException e) {
			e.printStackTrace();
		}
	}

	private List listFiles(String dirPath) throws IOException {
		return Files.walk(Paths.get(dirPath)).filter(Files::isRegularFile).map(Path::toFile)
				.collect(Collectors.toList());
	}

	public static void main(String args[]) {
		Converter Converter = new Converter();
		Converter.convert(args[0]);
	}
}

ただ、この段階で座標を変換しようとすると少し面倒だったので、出力される数値は生データのままです。

ここまで書かかなくても、わかる人はさっさと実行されていると思うのですけれども、反響(意味わかんねえよという苦情)が多ければ、ボタン操作で必要な情報を抜き出せるプログラムを公開するかもしれません。

作成すること自体は簡単なのですけれども、配布するためにはライセンスと利用規約を調べないといけないので、そこが面倒で時間がかかるのです。

座標データの準備ができましたら描画に移ります。

(2)座標データを Leaflet で描画する

データの準備ができましたら、走行経路を図形として地図上に描画します。

この目的で非常によく使われているライブラリに Leaflet.js というものがあります。Facebook でも Foursquare でも、どこでも使われているので、車輪の再発明を行うよりも使えるものは使ったほうがいいです。

ライブラリは CDN で配布されいるので HTML ファイルの最初にリンクを貼っておけば、ネットワークに接続されているコンピュータで利用できます。

使い方は単純なので公式のチュートリアルを見ながら自身で走行経路を描画されてもいいのですが、folium という Python のライブラリを使うと自動的に HTML ファイルとスクリプトを生成してくれるらしく、少し調べると先人の試行錯誤の記録がたくさん見つかります。

非常にありがたいことです。


Python + folium で Strava の "全"記録を地図で可視化 ? – hibomaの日記
https://hiboma.hatenadiary.jp/entry/2017/09/13/094056


Leaflet: Mapping Strava runs/polylines on Open Street Map · Mark Needham
https://markhneedham.com/blog/2017/04/29/leaflet-strava-polylines-osm/


ざっくりと見た感じでは、これを使うのが一番簡単そうに見えましたので、使ってみることにしますけれども、本音を述べると「なんだかなぁ」という気もします。

だって、普通の人のパソコンには Java も Python もインストールされてないじゃないですか(あと個人的にはあまり Python が好きではありません)。

取り敢えず、さきほど FIT ファイルから抜き出した座標データをそのまま使うのであれば、以下のように改編してやる必要があります。

#!/usr/bin/env python3
import os, sys, codecs, folium

def make(path, center):

    attributes='&copy; <a href="http://osm.org/copyright">OpenStreetMap</a> contributors'
    my_map = folium.Map(center, zoom_start=9,attr=attributes)
    
    folium.TileLayer('openstreetmap').add_to(my_map)
    folium.TileLayer('stamenterrain').add_to(my_map)
    folium.LayerControl().add_to(my_map)
    
    filelist = os.listdir(path)
    files = [file for file in filelist if file[-4:].lower() == ".csv"]
    for file in files:
        locations = []
        fin = codecs.open(path + "/" + file, "r", "utf-8")
        lines = fin.readlines()
        for line in lines:
            list = line.replace('"', '').strip().split(",")
            if list[0] != "Data" or list[6] != "position_lat" or list[9] != "position_long":
                continue
            try:
                lat = float(list[7]) * 180 / 2**31
                lon = float(list[10]) * 180 / 2**31              
                if isinstance(lat,float) and isinstance(lon,float):
                    locations.append(tuple([lat,lon]))
            except ValueError:
                continue
        if len(locations) > 1:        
            folium.PolyLine(locations=locations,color="red").add_to(my_map)
    my_map.save("./myroutes.html")

if __name__ == "__main__":
    dir_path = sys.argv[1]
    # center = sys.argv[2]
    center = [35.01848, 135.7799]
    make(dir_path, center)

各変数の内容を変えるとズームレベルや中心の座標、タイルの種類、走行経路の表示色を変更できます。

実行環境はこんな感じです。

$ python -V
Python 3.6.4 :: Anaconda, Inc.
$ pip -V
pip 19.2.3 from /home/buran/anaconda3/lib/python3.6/site-packages/pip (python 3.6)
$ python
>>> folium.__version__
'0.10.0'

(3)描画された地図(HTMLファイル)を表示する

folium で HTML ファイルを生成した場合には自動的にスクリプトファイルも読み込まれますので、このステップは必要ありません。

それ以外の場合には leaflet.js で地図を表示する HTML ファイルを作成し、そこに座標ファイルから作成された走行経路(具体的には polyline )を表示するスクリプトをあわせて読み込まないとなりません。

おそらく、こんな形になると思います。

<!DOCTYPE html>
<html>
<head>
  <title>My Ride Logs</title>
  <meta charset="utf-8" />
  <meta name="viewport" content="width=device-width, initial-scale=9.0">
  <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/leaflet/1.5.1/leaflet.css" crossorigin="" />
  <script src="https://cdnjs.cloudflare.com/ajax/libs/leaflet/1.5.1/leaflet.js" crossorigin=""></script>
  <style>html, body {width: 100%;height: 100%;margin: 0;padding: 0;}</style>
  <style>#mapid {position:absolute;top:0;bottom:0;right:0;left:0;}</style>
  <meta name="viewport" content="width=device-width,initial-scale=1.0, maximum-scale=1.0, user-scalable=no" />
</head>
<body>
  <div id="mapid"></div>
  <script src="./myridelogs.js"></script>
</body>
</html>

詳しくは公式のチュートリアルを参照してください。

これに走行経路を描画するスクリプトを読み込ます。

ここでは myridelogs.js という名前のファイルを読み込んでいるので、これを再利用されるのであれば同じ名前のファイルを用意して HTML ファイルと同じディレクトリの中に入れて下さい。

L.tileLayer(
  'https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png', {
    maxZoom: 19,
    attribution: 'Map data © OpenStreetMap contributors, ' +
      'CC-BY-SA, ' +
      'Imagery © Mapbox',
    id: 'mapbox.streets'
  }).addTo(mymap);

var polyLine089 = L.polyline(
  [
    [48.12477, 11.56289],
    [48.12180, 11.56864],
    [48.12163, 11.56827],
    // 無駄に長いので以下略
  ], {
    "bubblingMouseEvents": true,
    "color": "green",
    "dashArray": null,
    "dashOffset": null,
    "fill": false,
    "fillColor": "red",
    "fillOpacity": 0.2,
    "fillRule": "evenodd",
    "lineCap": "round",
    "lineJoin": "round",
    "noClip": false,
    "opacity": 1.0,
    "smoothFactor": 1.0,
    "stroke": true,
    "weight": 3
  }
).addTo(mymap);

ここまでで用意できた HTML をウェブブラウザで読み込むと、サイクリングやランニングの走行記録を地図上に表示することができます。

やろうと思えば、サイクリングは青、ランニングは赤、スイミングは緑などと色を変えて同一の地図上に一緒に表示することも可能です。

やってること自体は単純なので、すぐにでも実現できるのではないかと思われますけれども、プログラムを自分で書いて実行しないといけないので、その部分は少し大変かも知れません。


追記:サイコンの FIT ファイルから位置情報を読み込んで地図上に線を引くアプリをつくりました

アプリを使ってライド記録を地図上に描こう