2019年1月21日月曜日

gpxのログをgrid patternで表示してみる

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()

0 件のコメント:

コメントを投稿

麻のボディタオル

2018年の秋(まだ、自転車を封印してない)、 近江上布伝統産業会館 で、興味からボディタオルを購入した。 お、よかった。: 自然派パン工房 ふるさとの道 ほぼ毎日風呂で使ってきて、ついに寿命がきたようだ。 お店の方に、「糸が痩せて破れてくる」まで使える、と...