Source code for ezgpx.plotters.matplotlib_plotter

import os
from math import isclose
from typing import Optional, Tuple

import matplotlib
import matplotlib.pyplot as plt
from mpl_toolkits.basemap import Basemap

from .plotter import Plotter


[docs] class MatplotlibPlotter(Plotter): """ GPX plotter based on Matplotlib. """
[docs] def plot( self, figsize: Tuple[int, int] = (16, 9), size: float = 5, color: str = "#FFA800", cmap: Optional[matplotlib.colors.Colormap] = None, colorbar: bool = False, start_point_color: Optional[str] = None, stop_point_color: Optional[str] = None, waypoints_color: Optional[str] = None, background: Optional[str] = None, offset_percentage: float = 0.04, dpi: int = 96, title: Optional[str] = None, title_fontsize: int = 20, watermark: bool = False, file_path: str = None, ): """ Plot GPX using Matplotlib. Args: figsize (Tuple[int, int], optional): Width and height of the plot. Defaults to (16, 9). size (float, optional): Size of the track. Defaults to 5. color (str, optional): Color of the track. Possible choice are: "lat", "lon", "ele", "speed", "pace", "vertical_drop", "ascent_rate", "ascent_speed" or any valid color string. Defaults to "#FFA800". cmap (Optional[matplotlib.colors.Colormap], optional): Colormap to use when color is not a fixed color value. Defaults to None. colorbar (bool, optional): Add colorbar. Defaults to False. start_point_color (Optional[str], optional): Color of the first point. Defaults to None. stop_point_color (Optional[str], optional): Color of the last point. Defaults to None. waypoints_color (Optional[str], optional): Color of the way points. Defaults to None. background (Optional[str], optional): Map tiles to use. Possible choice are: None, "bluemarble", "shadedrelief", "etopo", "World_Imagery", "wms" or any server supported by `mpl_toolkits.basemap.Basemap.arcgisimage`. Defaults to None. offset_percentage (float, optional): Offset percentage to apply to the track bounding box. Defaults to 0.04. dpi (int, optional): Resolution of the animation. Defaults to 96. interval (float, optional): Interval between frames of the animation. Defaults to 20. bitrate (int, optional): Bit-rate of the animation. Defaults to 1800. repeat (bool, optional): Repeat the animation when viewed. Defaults to True. title (Optional[str], optional): Title of the plot. Defaults to None. title_fontsize (int, optional): Font size of the title of the plot. Defaults to 20. watermark (bool, optional): Watermark. Defaults to False. file_path (str, optional): Path to save the plot. Defaults to None. Raises: FileNotFoundError: Provided path does not exist. Returns: matplotlib.Figure: Plot of the GPX. """ dynamic_colors = [ "lat", "lon", "ele", "speed", "pace", "vertical_drop", "ascent_rate", "ascent_speed", ] # Create dataframe containing data from the GPX file values = ["lat", "lon"] if color in dynamic_colors: values.append(color) self._df = self._gpx.to_pandas(values) # Create figure fig = plt.figure(figsize=figsize) # Compute track boundaries min_lat, min_lon, max_lat, max_lon = [b.value for b in self._gpx.trkpt_bounds()] # Compute default offset delta_lat = abs(max_lat - min_lat) delta_lon = abs(max_lon - min_lon) delta_max = max(delta_lat, delta_lon) offset = delta_max * offset_percentage # Add default offset min_lat = max(-90, min_lat - offset) max_lat = min(max_lat + offset, 90) min_lon = max(-180, min_lon - offset) max_lon = min(max_lon + offset, 180) # Update min/max lat/lon to achieve correct aspect ratio lat_offset = 1e-5 lon_offset = 1e-5 delta_lat = abs(max_lat - min_lat) delta_lon = abs(max_lon - min_lon) r = delta_lon / delta_lat # Current map aspect ratio r_ref = figsize[0] / figsize[1] # Target map aspect ratio tolerance = 1e-3 while not isclose(r, r_ref, abs_tol=tolerance): if r > r_ref: min_lat = max(-90, min_lat - lat_offset) max_lat = min(max_lat + lat_offset, 90) delta_lat = max_lat - min_lat if r < r_ref: min_lon = max(-180, min_lon - lon_offset) max_lon = min(max_lon + lon_offset, 180) delta_lon = max_lon - min_lon r = delta_lon / delta_lat # Create map map_ = Basemap( projection="cyl", llcrnrlon=min_lon, llcrnrlat=min_lat, urcrnrlon=max_lon, urcrnrlat=max_lat, ) # Add background if background is None: pass elif background == "bluemarble": map_.bluemarble() elif background == "shadedrelief": map_.shadedrelief() elif background == "etopo": map_.etopo() elif background == "World_Imagery": map_.arcgisimage(service=background, dpi=dpi) elif background == "wms": wms_server = "http://www.ga.gov.au/gis/services/topography/Australian_Topography/MapServer/WMSServer" wms_server = "http://wms.geosignal.fr/metropole?" map_.wmsimage( wms_server, layers=["Communes", "Nationales", "Regions"], verbose=True ) else: map_.arcgisimage(service=background, dpi=dpi, verbose=True) # Scatter track points if color in dynamic_colors: im = map_.scatter( self._df["lon"], self._df["lat"], s=size, c=self._df[color], cmap=cmap, ) else: im = map_.scatter(self._df["lon"], self._df["lat"], s=size, color=color) # Scatter start point with different color if start_point_color: map_.scatter( self._df["lon"][0], self._df["lat"][0], marker="^", color=start_point_color, ) # Scatter stop point with different color if stop_point_color: map_.scatter( self._df["lon"].iloc[-1], self._df["lat"].iloc[-1], marker="h", color=stop_point_color, ) # Scatter way points with different color if waypoints_color: for waypoint in self._gpx.wpt: x, y = map_(waypoint.lon, waypoint.lat) # Project way point map_.scatter( x, y, marker="D", color=waypoints_color ) # Scatter way point # Colorbar if colorbar: fig.colorbar(im) # Add title if title is not None: if watermark: fig.suptitle(title + "\n[made with ezGPX]", fontsize=title_fontsize) else: fig.suptitle(title, fontsize=title_fontsize) # Set figure layout fig.tight_layout() # Save plot if file_path is not None: # Check if provided path exists directory_path = os.path.dirname(os.path.realpath(file_path)) if not os.path.exists(directory_path): raise FileNotFoundError("Provided path does not exist") fig.savefig(file_path) return fig