import os
import warnings
from typing import Optional
import numpy as np
import plotly.graph_objects as go
from .plotter import Plotter
[docs]
class PlotlyPlotter(Plotter):
"""
GPX plotter based on Plotly.
"""
[docs]
def plot(
self,
tiles: str = "open-street-map", # "open-street-map"
mode: str = "lines", # "lines", "markers+lines"
color: str = "#FFA800",
start_point_color: Optional[str] = None,
stop_point_color: Optional[str] = None,
waypoints_color: Optional[str] = None,
title: Optional[str] = None,
zoom: Optional[float] = None,
file_path: Optional[str] = None,
):
"""
Plot (animation) GPX using Plotly.
Args:
tiles (str, optional): Map tiles to use. Defaults to
"open-street-map".
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.
title (Optional[str], optional): Title of the plot.
Defaults to None.
zoom (float, optional): Zoom. Defaults to 12.0.
file_path (Optional[str], optional): Path to save the plot. Defaults to None.
Returns:
_type_: _description_
"""
self._df = self._gpx.to_polars()
center_lat, center_lon = self._gpx.center()
min_lat, min_lon, max_lat, max_lon = self._gpx.bounds()
if zoom is None:
# Determine zoom level based on bounds
lat_range = max_lat - min_lat
lon_range = max_lon - min_lon
max_range = max(lat_range, lon_range)
zoom = 8 - np.log2(max_range + 1e-6)
# Create map and scatter
fig = go.Figure(
go.Scattermap(
mode=mode,
lon=self._df["lon"],
lat=self._df["lat"],
marker={"size": 5, "color": color},
)
)
# Scatter start and stop points with different color
if start_point_color:
fig.add_trace(
go.Scattermap(
mode="markers",
lon=[self._df["lon"].iloc[0]],
lat=[self._df["lat"].iloc[0]],
marker={"size": 5, "color": start_point_color},
)
)
if stop_point_color:
fig.add_trace(
go.Scattermap(
mode="markers",
lon=[self._df["lon"].iloc[-1]],
lat=[self._df["lat"].iloc[-1]],
marker={"size": 5, "color": stop_point_color},
)
)
# Scatter way points with different color
if waypoints_color:
for waypoint in self._gpx.gpx.wpt:
fig.add_trace(
go.Scattermap(
mode="markers",
lon=waypoint.lon,
lat=waypoint.lat,
marker={"size": 5, "color": waypoints_color},
)
)
# Update layout for nice display
fig.update_layout(
margin={"l": 0, "t": 0, "b": 0, "r": 0},
map={
"center": {"lon": center_lon, "lat": center_lat},
"style": tiles,
"zoom": zoom,
},
title={"text": title},
)
# 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):
warnings.warn("Provided path does not exist")
return
if file_path.endswith(".html"):
fig.write_html(file_path)
elif file_path.endswith(".json"):
fig.write_json(file_path)
else:
fig.write_image(file_path)
return fig