Notebook

これは日々の作業を通して学んだことや毎日の生活で気づいたことをを記録しておく備忘録である。

HTML ファイル生成日時: 2025/12/23 00:01:16.512 (台灣標準時)

Python を使って地図上に移動の軌跡を描く方法

Python を使って、地図をダウンロードし、その地図上に GPS で記録した移動 の軌跡を描く方法を調べてみたでござる。以下はその記録でござる。

希望する領域の地図をダウンロードする方法は、「Python を使って指定した範 囲の地図をダウンロードする方法」に記録したでござる。

GPX ファイルを読み込み、 GPS データを取得する方法は、「GPX ファイルを読み込んで 経度緯度標高を抽出する方法」に記録したでござる。

ダウンロードした地図上に点を打ったり、線を描いたりするには、座標として 経度と緯度を使ってプロットすればよいようでござる。

以下のようなスクリプトを用意してみたでござる。


#!/usr/pkg/bin/python3

#
# Time-stamp: <2025/12/22 23:54:39 (UT+08:00) daisuke>
#

# importing argparse module
import argparse

# importing sys module
import sys

# importing pathlib module
import pathlib

# importing numpy module
import numpy

# importing gpxpy module
import gpxpy
import gpxpy.gpx

# importing contextily module
import contextily

# importing matplotlib module
import matplotlib.backends.backend_agg
import matplotlib.figure

# initialising a parser
parser = argparse.ArgumentParser (description='Showing GPX tracks on a map')

# choices
list_mapsource = ('mapnik', 'opentopomap')
list_zoomadjust = (0, 1)

# adding arguments
parser.add_argument ('-o', '--output', default='map.png', \
                     help='output file (default: map.png)')
parser.add_argument ('-l', '--longitude', type=float, default=-999.9, \
                     help='longitude in degree')
parser.add_argument ('-b', '--latitude', type=float, default=-999.9, \
                     help='latitude in degree')
parser.add_argument ('-w', '--width', type=float, default=-999.9, \
                     help='width of the map in degree')
parser.add_argument ('-s', '--mapsource', default='mapnik', \
                     choices=list_mapsource, \
                     help='source of the map (default: mapnik)')
parser.add_argument ('-m', '--margin', type=float, default=40.0, \
                     help='margin of the map in percent (default: 40.0)')
parser.add_argument ('-r', '--resolution', type=float, default=225.0, \
                     help='resolution of output file in DPI (default: 225)')
parser.add_argument ('-z', '--zoomadjust', type=int, default=0, \
                     choices=list_zoomadjust, \
                     help='zoom level adjustment (default: 0)')
parser.add_argument ('files', nargs='+', help='input GPX files')

# parsing arguments
args = parser.parse_args ()

# input parameters
file_output      = args.output
longitude_centre = args.longitude
latitude_centre  = args.latitude
width_deg        = args.width
map_source       = args.mapsource
resolution_dpi   = args.resolution
zoom_adjust      = args.zoomadjust
list_input_files = args.files
margin_percent   = args.margin

# map source
if (map_source == 'mapnik'):
    source = contextily.providers.OpenStreetMap.Mapnik
elif (map_source == 'opentopomap'):
    source = contextily.providers.OpenTopoMap

# check of longitude, latitude, and width
if ( ( (longitude_centre != -999.9) and (latitude_centre != -999.9) \
       and (width_deg != -999.9) )
     and ( (longitude_centre < -180.0) or (longitude_centre > +180.0) \
     or (latitude_centre < -90.0) or (latitude_centre > +90.0) \
     or (width_deg <= 0.0) ) ):
    # printing message
    print (f'#')
    print (f'# ERROR: Something is wrong with longitude or latitude or width!')
    print (f'# ERROR: Longitude: {longitude_centre} deg')
    print (f'# ERROR: Latitude:  {latitude_centre} deg')
    print (f'# ERROR: Width:     {width_deg} deg')
    print (f'#')
    # stopping the script
    sys.exit (0)

# making an empty list for storing data
list_gpx_files = []
data_longitude = []
data_latitude  = []
data_elevation = []

# initialisation of lon_min, lon_max, lat_min, and lat_max
lon_min = +999999.9
lon_max = -999999.9
lat_min = +999999.9
lat_max = -999999.9

# making pathlib object
path_output = pathlib.Path (file_output)

# existing check of output file
if (path_output.exists ()):
    # printing message
    print (f'#')
    print (f'# ERROR: output file \"{file_output}\" DOES exist!')
    print (f'# ERROR: stopping the script...')
    print (f'#')
    # stopping the script
    sys.exit (0)

# processing GPX files one-by-one
for file_gpx in list_input_files:
    # making pathlib object
    path_gpx = pathlib.Path (file_gpx)
    # if the file is NOT a GPX file, then skip
    if (path_gpx.suffix != '.gpx'):
        # printing message
        print (f'#')
        print (f'# WARNING: the file \"{file_gpx}\" is NOT a GPX file!')
        print (f'# WARNING: skipping the file \"{file_gpx}\"...')
        print (f'#')
        # skipping the file
        continue
    # if the file does not exist, the skip
    if not (path_gpx.exists ()):
        # printing message
        print (f'#')
        print (f'# WARNING: the file \"{file_gpx}\" does NOT exist!')
        print (f'# WARNING: skipping the file \"{file_gpx}\"...')
        print (f'#')
        # skipping the file
        continue
    # opening GPX file
    with open (file_gpx, 'r') as fh_in:
        # reading data from GPX file
        data_gpx = gpxpy.parse (fh_in)
    # making empty lists for storing data
    list_longitude = []
    list_latitude  = []
    list_elevation = []
    # extracting longitude, latitude, and elevation
    for track in data_gpx.tracks:
        for segment in track.segments:
            for point in segment.points:
                # appending data to lists
                list_longitude.append (point.longitude)
                list_latitude.append (point.latitude)
                list_elevation.append (point.elevation)
                # checking minimum and maximum values
                if (point.longitude < lon_min):
                    lon_min = point.longitude
                if (point.longitude > lon_max):
                    lon_max = point.longitude
                if (point.latitude < lat_min):
                    lat_min = point.latitude
                if (point.latitude > lat_max):
                    lat_max = point.latitude
    # appending data to data_longitude, data_latitude, and data_elevation
    data_longitude.append (list_longitude)
    data_latitude.append (list_latitude)
    data_elevation.append (list_elevation)

# longitude and latitude centre of the map and width of the map
if ( (longitude_centre < -999.0) and (latitude_centre < -999.0) \
     and (width_deg < -999.0) ):
    longitude_centre = (lon_max + lon_min) / 2.0
    latitude_centre  = (lat_max + lat_min) / 2.0
    width_longitude = lon_max - lon_min
    width_latitude  = lat_max - lat_min
    if (width_longitude > width_latitude):
        width_deg = width_longitude * (1.0 + margin_percent / 100.0)
    else:
        width_deg = width_latitude * (1.0 + margin_percent / 100.0)
    
# west end, east end, south end, and north end of the region to plot in deg
west_end_deg  = longitude_centre - width_deg * 0.5
east_end_deg  = longitude_centre + width_deg * 0.5
south_end_deg = latitude_centre - width_deg * 0.5
north_end_deg = latitude_centre + width_deg * 0.5
region        = (west_end_deg, east_end_deg, south_end_deg, north_end_deg)

# creating fig, canvas, and ax objects
fig    = matplotlib.figure.Figure ()
canvas = matplotlib.backends.backend_agg.FigureCanvasAgg (fig)
ax     = fig.add_subplot (111)

# settings of output map
ax.set_aspect("equal")
ax.axis ("off")
ax.axis (region)
if (zoom_adjust == 1):
    contextily.add_basemap (ax, crs="EPSG:4326", source=source, \
                            zoom_adjust=zoom_adjust)
else:
    contextily.add_basemap (ax, crs="EPSG:4326", source=source)
fig.subplots_adjust (left=0.0, right=1.0, bottom=0.0, top=1.0)

# visualising GPX tracks
for i in range ( len (data_longitude) ):
    ax.plot (data_longitude[i], data_latitude[i], \
             linestyle='-', linewidth=2, color='red', alpha=0.7)
    ax.plot (data_longitude[i][0], data_latitude[i][0], \
             linestyle='None', marker='o', markersize=7, \
             color='blue', alpha=0.5)
    ax.plot (data_longitude[i][-1], data_latitude[i][-1], \
             linestyle='None', marker='s', markersize=7, \
             color='green', alpha=0.5)

# creating a map
fig.savefig (file_output, dpi=resolution_dpi, \
             bbox_inches="tight", transparent=True)

実行してみた結果は以下の通りでござる。


% python3 contextily_show_gps_track_00.py -h
usage: contextily_show_gps_track_00.py [-h] [-o OUTPUT] [-l LONGITUDE]
                                       [-b LATITUDE] [-w WIDTH]
                                       [-s {mapnik,opentopomap}] [-m MARGIN]
                                       [-r RESOLUTION] [-z {0,1}]
                                       files [files ...]

Showing GPX tracks on a map

positional arguments:
  files                 input GPX files

options:
  -h, --help            show this help message and exit
  -o, --output OUTPUT   output file (default: map.png)
  -l, --longitude LONGITUDE
                        longitude in degree
  -b, --latitude LATITUDE
                        latitude in degree
  -w, --width WIDTH     width of the map in degree
  -s, --mapsource {mapnik,opentopomap}
                        source of the map (default: mapnik)
  -m, --margin MARGIN   margin of the map in percent (default: 40.0)
  -r, --resolution RESOLUTION
                        resolution of output file in DPI (default: 225)
  -z, --zoomadjust {0,1}
                        zoom level adjustment (default: 0)

% python3 contextily_show_gps_track_00.py -s mapnik -m 50 -z 1 -o luofu_mapnik.png luofu.gpx

fig_202512/luofu_mapnik.png

複数の GPX ファイルの中のデータを読み込み、移動の軌跡を描いてみたでござる。


% python3 contextily_show_gps_track_00.py -s mapnik -m 50 -z 1 -o shueshan_mapnik.png shueshan_*.gpx

fig_202512/shueshan_mapnik.png


Frequently accessed files

  1. Misc___Taiwan/20240207_00.html
  2. Computer___TeX/20231107_00.html
  3. Computer___TeX/20240410_00.html
  4. Misc___Taiwan/20240819_00.html
  5. Book___Chinese/20240424_00.html
  6. Computer___TeX/20230726_01.html
  7. Computer___TeX/20240411_00.html
  8. Misc___Japan/20240718_00.html
  9. Computer___Python/20250330_00.html
  10. Misc___Taiwan/20240903_01.html
  11. Computer___Network/20230516_00.html
  12. Computer___NetBSD/20250301_01.html
  13. Computer___Network/20241214_00.html
  14. Computer___TeX/20240414_00.html
  15. Computer___NetBSD/20230119_00.html
  16. Computer___Network/20240130_00.html
  17. Computer___TeX/20240414_01.html
  18. Computer___FreeBSD/20220621_0.html
  19. Computer___Python/20220518_0.html
  20. Computer___Python/20220715_0.html
  21. Computer___NetBSD/20250409_00.html
  22. Computer___NetBSD/20240810_00.html
  23. Computer___NetBSD/20250113_00.html
  24. Computer___Network/20220413_1.html
  25. Computer___Python/20240101_00.html
  26. Computer___Debian/20230102_00.html
  27. Computer___NetBSD/20240805_03.html
  28. Computer___NetBSD/20241102_00.html
  29. Misc___Japan/20240610_00.html
  30. Computer___Hardware/20240820_00.html


HTML file generated by Kinoshita Daisuke.