gpxpy で
extentionsが使えるようになったみたいなので、
少し試してみる意味もあって、
gpxのログを読み込んで、その概要を掃き出すスクリプトを作ってみた。
せっかくなので、どの方面へ出掛けたログかを直感的に見えるように、
小さな画像ファイルを作成する機能も付けてみた。ファイルマネージャとかで気楽にpreviewできるはず。
ログの概要出力はこんな感じ。
私の場合は、extentionsの中身はハートレートだけなので、割と簡単?だった。
画像化の方は、
地理院の三次メッシュ のどこを通過したか
をグリッドで表現したつもり。
三次メッシュは、ひとコマ約1kmということだが、
私の場合は自転車のログがほとんどなので、ひとコマ約5kmにしてみた。
以下がその出力例。
上は、mlterm(xterm も ok)を使えば、lsix で画像表示可能という記事を見たので、
インストして試してみた次第。
また、 MOONGIFT にお世話になってしまった。
正しい画像が出来ているかは検証してないが、
まあ、雰囲気だけだから、という感じ。
出発点に何かキーワードが欲しいけど、ノーアイデア。
地理院から
市町村のデータ(市区町村役場データ)を
落としてきて、スタート地点に一番近い庁舎の住所を番地抜きで加えてみた。
引っ越してくる前のやつとの区別はできる。
あと、古いgpxデバイスのログが扱えるかが不安。
[201906]
位置の情報量としては、
同じ地理院の位置参照情報ダウンロードサービス
の方が充実しているが、ファイルがでかすぎる。
[201903] 古いデータもやってみた。
gpsの機種が古いので、extensionの判定を追加する必要があったが、なんとか表示できた。
あと、日付も画像に追加した。
[201904]
gpsのログは、フォルダにまとめているので、
ファイルマネージャーで使っているrangerの設定ファイル(rfile.conf)に、
サムネイル表示のため `sxiv -t` を追加した。
これで、いつ頃どの方面に出掛けたのか分かり易くなった。かも。
ext png, has sxiv, X, flag f = sxiv -t *.png
以下がpythonスクリプト(前のやつ)。
#!~/.pyenv/shims/python
"""Make grid pattern image of gpx log file."""
import sys
import math
import logging
from pathlib import Path
from statistics import mean
import gpxpy
import logzero
from PIL import Image, ImageDraw, ImageFont
from pytz import timezone
from dateutil import parser
a_div = math.pi / 4.0
ang_lst = [[-0.5 * a_div, 0.5 * a_div, "NN"],
[0.5 * a_div, 1.5 * a_div, "NE"],
[1.5 * a_div, 2.5 * a_div, "EE"],
[2.5 * a_div, 3.5 * a_div, "SE"],
[3.5 * a_div, 4.5 * a_div, "SS"],
[-4.5 * a_div, -3.5 * a_div, "SS"],
[-3.5 * a_div, -2.5 * a_div, "SW"],
[-2.5 * a_div, -1.5 * a_div, "WW"],
[-1.5 * a_div, -0.5 * a_div, "NW"]]
def set_direction(s_p, e_p):
"""Calculate distance and direction."""
dist_m = gpxpy.geo.distance(s_p[0], s_p[1], None, e_p[0], e_p[1], None)
ang_ra = math.atan2(e_p[1] - s_p[1], e_p[0] - s_p[0])
dir_char = "--"
if dist_m != 0:
for a_lst in ang_lst:
if a_lst[0] <= ang_ra <= a_lst[1]:
dir_char = a_lst[2]
return dist_m, dir_char
def show_info(g_f, info_lst, op_lst, img_file):
"""Show gpx info."""
jt_st, jt_en, mv_dist, stp_dist, max_speed, mv_time, stp_time, up_hill, down_hill = info_lst
hr_mean, hr_max, lat_min, lat_max, lon_min, lon_max, dis_far, dir_st = op_lst
logzero.logger.info("# {0:s}".format(str(g_f.name)))
mes_str = "- time ({0:%Y-%m-%d %H:%M:%S %Z})".format(jt_st)
mes_str += " - ({0:%Y-%m-%d %H:%M:%S %Z})".format(jt_en)
logzero.logger.info(mes_str)
mes_st = '- trip {0:.1f}km'.format(mv_dist)
mes_st += '({0:d}({1:d})min)'.format(int(mv_time), int(stp_time))
mes_st += ' spd.{0:5.1f}({1:.1f})km'.format(mv_dist / mv_time * 60.0, max_speed * 3.6)
mes_st += ' u/d({0:.1f}/{1:.1f})'.format(up_hill, down_hill)
logzero.logger.info(mes_st)
mes_st += ' {0:6.1f} m'.format(stp_dist / 1.0)
mes_st = "- HR mean {0:d} max {1:d}".format(hr_mean, hr_max)
mes_st += " (rat {0:6.3f}-{1:6.3f} lon {2:7.3f}-{3:7.3f})".format(lat_min, lat_max, lon_min, lon_max)
logzero.logger.info(mes_st)
mes_st = "- farthest {0:.1f}km {1:s}".format(dis_far, dir_st)
logzero.logger.info(mes_st)
logzero.logger.info("- Img {0:s}".format(img_file.name))
def parse_track(gpx_obj):
"""Parse track data."""
hr_val, lat_val, lon_val = [], [], []
for track in gpx_obj.tracks:
for segment in track.segments:
for point in segment.points:
if point.extensions:
ext_obj = int(point.extensions[0].getchildren()[0].text.strip())
else:
ext_obj = 0
hr_val.append(ext_obj)
lat_val.append(point.latitude)
lon_val.append(point.longitude)
return hr_val, lat_val, lon_val
def to_mapcode(lat_v, lon_v):
"""Calculate geo code by geographic code system."""
code_1st_a, mod_a = divmod(lat_v * 60.0, 40.0)
code_1st_b, mod_b = int(lon_v - 100.0), lon_v - int(lon_v)
code_2nd_a, mod_c = divmod(mod_a, 5.0)
code_2nd_b, mod_d = divmod(mod_b * 60.0, 7.5)
code_3rd_a, mod_e = divmod(mod_c * 60.0, 30.0)
code_3rd_b, mod_f = divmod(mod_d * 60.0, 45.0)
mod_e += mod_f
code_1st = int(code_1st_a) * 100 + int(code_1st_b)
code_2nd = int(code_2nd_a) * 10 + int(code_2nd_b)
code_3rd = int(code_3rd_a) * 10 + int(code_3rd_b)
mesh_code = code_1st * 10000 + code_2nd * 100 + code_3rd
return mesh_code
def diff_mapcode(pre_c, now_c):
"""Diff mapcode."""
c_str = "{0:08d}".format(pre_c)
pre_a = int(c_str[0:2]) * 100 + int(c_str[4:5]) * 10 + int(c_str[6:7])
pre_l = int(c_str[2:4]) * 100 + int(c_str[5:6]) * 10 + int(c_str[7:8])
c_str = "{0:08d}".format(now_c)
now_a = int(c_str[0:2]) * 100 + int(c_str[4:5]) * 10 + int(c_str[6:7])
now_l = int(c_str[2:4]) * 100 + int(c_str[5:6]) * 10 + int(c_str[7:8])
return pre_a - now_a, pre_l - now_l, now_a, now_l
def calc_geocode(lat_val, lon_val):
"""Calculate geo code."""
code_pre, code_now = 0, 0
code_lst, inc_lst = [], []
diff_a, diff_l = 0, 0
for v_lat, v_lon in zip(lat_val, lon_val):
mesh_code = to_mapcode(v_lat, v_lon)
code_lst.append(mesh_code)
code_pre = code_now
code_now = mesh_code
if mesh_code != code_pre:
diff_a, diff_l, m_a, m_l = diff_mapcode(code_now, code_pre)
inc_lst.append([diff_a, diff_l, m_a, m_l])
return inc_lst
def gen_grid(inc_lst, img_f, mv_dist, g_mes):
"""Generate grid path image."""
image = Image.new(mode='L', size=(600, 600), color=255)
skip_div = 5
n_div = int(max((mv_dist + 3.0 * skip_div) * 2.5, 2) / 2.0)
n_step = int(image.width / n_div)
draw = ImageDraw.Draw(image)
y_start, y_end = 0, image.height
x_start, x_end = 0, image.width
for x in range(0, image.width, n_step * skip_div):
line = ((x, y_start), (x, y_end))
draw.line(line, fill=140)
for y in range(0, image.height, n_step * skip_div):
line = ((x_start, y), (x_end, y))
draw.line(line, fill=140)
m_xo, m_yo = inc_lst[1][3], inc_lst[1][2]
for m_po in inc_lst[1:-1]:
p_x = int(image.width / 2 + (m_po[3] - m_xo - 0.5) * n_step)
p_y = int(image.height / 2 - (m_po[2] - m_yo - 0.5) * n_step)
c_col = 32 if (m_po[2] == m_xo and m_po[3] == m_yo) else 120
draw.rectangle((p_x, p_y, p_x + n_step, p_y + n_step),
fill=(c_col), outline=(128))
dd_cell = int(image.width / 15)
p_x = int(image.width / 2 - 0.5 * dd_cell)
p_y = int(image.height / 2 + 0.5 * dd_cell)
draw.rectangle((p_x, p_y, p_x + dd_cell, p_y - dd_cell),
fill=(2), outline=(2))
d_font = ImageFont.truetype("arial.ttf", 82)
draw.text((0, image.height / 22), g_mes, font=d_font)
del draw
image.save(img_f, 'png', quality=70, optimize=True)
def parse_gpx(g_f, g_obj):
"""Parse gpx file."""
st_time, en_time = g_obj.get_time_bounds()
jst_st = timezone('UTC').localize(st_time).astimezone(timezone('Asia/Tokyo'))
jst_en = timezone('UTC').localize(en_time).astimezone(timezone('Asia/Tokyo'))
mv_time, stp_time, mv_dist, stp_dist, max_speed = g_obj.get_moving_data(stopped_speed_threshold=0.4)
up_hill, down_hill = g_obj.get_uphill_downhill()
info_lst = [jst_st, jst_en, mv_dist / 1000.0, stp_dist / 1000.0,
max_speed, mv_time / 60.0, stp_time / 60.0,
up_hill, down_hill]
hr_val, lat_val, lon_val = parse_track(g_obj)
st_point = [lat_val[1], lon_val[1]]
farthest = 0.0
for lat_p, lon_p in zip(lat_val, lon_val):
distance, dr_car = set_direction(st_point, [lat_p, lon_p])
if distance > farthest:
farthest = distance
dir_char = dr_car
op_lst = [int(mean(hr_val)), int(max(hr_val)),
min(lat_val), max(lat_val), min(lon_val), max(lon_val),
farthest / 1000., dir_char]
inc_lst = calc_geocode(lat_val, lon_val)
g_mes = " {0:s}{1:d}".format(op_lst[7], int(op_lst[6]))
g_mes += "({0:d}k)".format(int(info_lst[2]))
img_file_name = g_f.with_suffix('.png')
gen_grid(inc_lst, img_file_name, int(farthest / 1000), g_mes)
return info_lst, op_lst, img_file_name
def main():
"""Do main prcess."""
args = sys.argv
gpx_log_path = Path.cwd() / Path(args[1])
with open(gpx_log_path, 'r') as gpx_file:
gpx_obj = gpxpy.parse(gpx_file)
gspc_lst, op_lst, img_file = parse_gpx(gpx_log_path, gpx_obj)
show_info(gpx_log_path, gspc_lst, op_lst, img_file)
if __name__ == '__main__':
LOG_FORMAT = '%(color)s[%(module)s:%(lineno)d]%(end_color)s %(message)s'
FORMATTER = logzero.LogFormatter(fmt=LOG_FORMAT)
logzero.setup_default_logger(formatter=FORMATTER)
logzero.loglevel(logging.INFO)
logzero.logfile("/home/hogehoge/.logs/log.log", maxBytes=3e5, backupCount=3)
main()