Skip to content

Commit

Permalink
#3 Work in progress:
Browse files Browse the repository at this point in the history
-Add xmlns:xsi parsing and writing
-Suspend Extensions parsing and writing
-New parsing and writing test
-Add to_csv method
-Add merge method (for testing purpose)

[ci skip]
  • Loading branch information
FABallemand committed Jul 22, 2023
1 parent bbefddb commit 55c74c6
Show file tree
Hide file tree
Showing 11 changed files with 29,599 additions and 29,468 deletions.
130 changes: 95 additions & 35 deletions ezgpx/gpx/gpx.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import os
from typing import Optional, Union
from typing import Optional, Union, NewType
import logging
import webbrowser
from datetime import datetime
Expand All @@ -22,18 +22,31 @@
from ..gpx_writer import Writer
from ..utils import EARTH_RADIUS

GPX = NewType("GPX", object) # GPX forward declaration for type hint

class GPX():
"""
High level GPX object.
"""

def __init__(self, file_path: str):
self.file_path: str = file_path
self.parser: Parser = Parser(file_path)
self.gpx: Gpx = self.parser.gpx
self.writer: Writer = Writer(
self.gpx, precisions=self.parser.precisions, time_format=self.parser.time_format)
def __init__(self, file_path: Optional[str] = None) -> None:
if file_path is not None:
self.file_path: str = file_path
self.parser: Parser = Parser(file_path)
self.gpx: Gpx = self.parser.gpx
self.writer: Writer = Writer(
self.gpx, precisions=self.parser.precisions, time_format=self.parser.time_format)
else:
pass

def file_name(self) -> Union[str, None]:
"""
Return .gpx file name.
Returns:
str: File name.
"""
return os.path.basename(self.file_path)

def name(self) -> str:
"""
Expand Down Expand Up @@ -219,33 +232,6 @@ def avg_moving_pace(self) -> float:
"""
return self.gpx.avg_moving_pace()

def to_string(self) -> str:
"""
Convert the GPX object to a string.
Returns:
str: String representingth GPX object.
"""
return self.writer.gpx_to_string(self.gpx)

def to_gpx(self, path: str):
"""
Write the GPX object to a .gpx file.
Args:
path (str): Path to the .gpx file.
"""
self.writer.write(path)

def to_dataframe(self) -> pd.DataFrame:
"""
Convert GPX object to Pandas Dataframe.
Returns:
pd.DataFrame: Dataframe containing position data from GPX.
"""
return self.gpx.to_dataframe()

def remove_metadata(self):
"""
Remove metadata (ie: metadata will not be written when saving the GPX object as a .gpx file).
Expand Down Expand Up @@ -292,6 +278,80 @@ def simplify(self, tolerance: float = 2):
epsilon = degrees(tolerance/EARTH_RADIUS)
self.gpx.simplify(epsilon)

def merge(self, gpx: GPX):

if self.gpx.tag is None:
self.gpx.tag = gpx.gpx.tag
if self.gpx.creator is None:
self.gpx.creator = gpx.gpx.creator
if self.gpx.xmlns is None:
self.gpx.xmlns = gpx.gpx.xmlns
if self.gpx.version is None:
self.gpx.version = gpx.gpx.version
if self.gpx.xmlns_xsi is None:
self.gpx.xmlns_xsi = gpx.gpx.xmlns_xsi
if self.gpx.xsi_schema_location is None:
self.gpx.xsi_schema_location = gpx.gpx.xsi_schema_location
if self.gpx.xmlns_gpxtpx is None:
self.gpx.xmlns_gpxtpx = gpx.gpx.xmlns_gpxtpx
if self.gpx.xmlns_gpxx is None:
self.gpx.xmlns_gpxx = gpx.gpx.xmlns_gpxx
if self.gpx.xmlns_gpxtrk is None:
self.gpx.xmlns_gpxtrk = gpx.gpx.xmlns_gpxtrk
if self.gpx.xmlns_wptx1 is None:
self.gpx.xmlns_wptx1 = gpx.gpx.xmlns_wptx1
if self.gpx.metadata is None:
self.gpx.metadata = gpx.gpx.metadata
if self.gpx.wpt is None:
self.gpx.wpt = gpx.gpx.wpt
if self.gpx.rte is None:
self.gpx.rte = gpx.gpx.rte
if self.gpx.tracks is None:
self.gpx.tracks = gpx.gpx.tracks
if self.gpx.extensions is None:
self.gpx.extensions = gpx.gpx.extensions

def to_string(self) -> str:
"""
Convert the GPX object to a string.
Returns:
str: String representingth GPX object.
"""
return self.writer.gpx_to_string(self.gpx)

def to_dataframe(self, projection: bool = False) -> pd.DataFrame:
"""
Convert GPX object to Pandas Dataframe.
Returns:
pd.DataFrame: Dataframe containing position data from GPX.
"""
return self.gpx.to_dataframe(projection)

def to_gpx(self, path: str):
"""
Write the GPX object to a .gpx file.
Args:
path (str): Path to the .gpx file.
"""
self.writer.write(path)

def to_csv(
self,
path: str,
sep: str = ",",
header: bool = True,
index: bool = False):
"""
Write the GPX object track coordinates to a .csv file.
Args:
path (str): Path to the .csv file.
"""
self.to_dataframe().to_csv(path, sep=sep, header=header, index=index)

def _matplotlib_plot_text(
self,
fig: Figure,
Expand Down Expand Up @@ -384,7 +444,7 @@ def matplotlib_axes_plot(
self.gpx.project(projection) # Project all track points

# Create dataframe containing data from the GPX file
gpx_df = self.to_dataframe()
gpx_df = self.to_dataframe(projection=True)

# Scatter all track points
if elevation_color:
Expand Down
35 changes: 23 additions & 12 deletions ezgpx/gpx_elements/gpx.py
Original file line number Diff line number Diff line change
Expand Up @@ -351,24 +351,35 @@ def avg_moving_pace(self) -> float:
"""
return 60 / self.avg_moving_speed()

def to_dataframe(self) -> pd.DataFrame:
def to_dataframe(self, projection: bool = False) -> pd.DataFrame:
"""
Convert Gpx element to Pandas Dataframe.
Returns:
pd.DataFrame: Dataframe containing position data from GPX.
Pandas.DataFrame: Dataframe containing position data from GPX.
"""
route_info = []
for track in self.tracks:
for segment in track.trkseg:
for point in segment.trkpt:
route_info.append({
"latitude": point.lat,
"longitude": point.lon,
"elevation": point.ele,
"x": point._x,
"y": point._y
})

if projection:
for track in self.tracks:
for segment in track.trkseg:
for point in segment.trkpt:
route_info.append({
"latitude": point.lat,
"longitude": point.lon,
"elevation": point.ele,
"x": point._x,
"y": point._y
})
else:
for track in self.tracks:
for segment in track.trkseg:
for point in segment.trkpt:
route_info.append({
"latitude": point.lat,
"longitude": point.lon,
"elevation": point.ele
})
df = pd.DataFrame(route_info)
return df

Expand Down
69 changes: 39 additions & 30 deletions ezgpx/gpx_parser/parser.py
Original file line number Diff line number Diff line change
Expand Up @@ -260,7 +260,7 @@ def find_time(self, element, sub_element: str) -> Union[datetime, None]:
logging.debug(f"{element} has no attribute {sub_element}.")
return time_

def parse_bounds(self, bounds, tag: str ="bounds") -> Bounds:
def parse_bounds(self, bounds, tag: str ="bounds") -> Union[Bounds, None]:
"""
Parse boundsType element from GPX file.
Expand All @@ -281,7 +281,7 @@ def parse_bounds(self, bounds, tag: str ="bounds") -> Bounds:

return Bounds(tag, minlat, minlon, maxlat, maxlon)

def parse_copyright(self, copyright, tag: str ="copyright") -> Copyright:
def parse_copyright(self, copyright, tag: str ="copyright") -> Union[Copyright, None]:
"""
Parse copyrightType element from GPX file.
Expand All @@ -301,7 +301,7 @@ def parse_copyright(self, copyright, tag: str ="copyright") -> Copyright:

return Copyright(tag, author, year, licence)

def parse_email(self, email, tag: str ="email") -> Email:
def parse_email(self, email, tag: str ="email") -> Union[Email, None]:
"""
Parse emailType element from GPX file.
Expand All @@ -320,7 +320,7 @@ def parse_email(self, email, tag: str ="email") -> Email:

return Email(tag, id, domain)

def parse_extensions(self, extensions, tag: str ="extensions") -> Extensions:
def parse_extensions(self, extensions, tag: str ="extensions") -> Union[Extensions, None]:
"""
Parse extensionsType element from GPX file.
Expand All @@ -334,25 +334,26 @@ def parse_extensions(self, extensions, tag: str ="extensions") -> Extensions:
if extensions is None:
return None

display_color = self.find_text(extensions, "topo:DisplayColor")
distance = self.find_text(extensions, "topo:Distance")
total_elapsed_time = self.find_text(extensions, "topo:TotalElapsedTime")
moving_time = self.find_text(extensions, "topo:MovingTime")
stopped_time = self.find_text(extensions, "topo:StoppedTime")
moving_speed = self.find_text(extensions, "topo:MovingSpeed")
max_speed = self.find_text(extensions, "topo:MaxSpeed")
max_elevation = self.find_text(extensions, "topo:MaxElevation")
min_elevation = self.find_text(extensions, "topo:MinElevation")
ascent = self.find_text(extensions, "topo:Ascent")
descent = self.find_text(extensions, "topo:Descent")
avg_ascent_rate = self.find_text(extensions, "topo:AvgAscentRate")
max_ascent_rate = self.find_text(extensions, "topo:MaxAscentRate")
avg_descent_rate = self.find_text(extensions, "topo:AvgDescentRate")
max_descent_rate = self.find_text(extensions, "topo:MaxDescentRate")

return Extensions(tag, display_color, distance, total_elapsed_time, moving_time, stopped_time, moving_speed, max_speed, max_elevation, min_elevation, ascent, descent, avg_ascent_rate, max_ascent_rate, avg_descent_rate, max_descent_rate)

def parse_link(self, link, tag: str ="link") -> Link:
# display_color = self.find_text(extensions, "topo:DisplayColor")
# distance = self.find_text(extensions, "topo:Distance")
# total_elapsed_time = self.find_text(extensions, "topo:TotalElapsedTime")
# moving_time = self.find_text(extensions, "topo:MovingTime")
# stopped_time = self.find_text(extensions, "topo:StoppedTime")
# moving_speed = self.find_text(extensions, "topo:MovingSpeed")
# max_speed = self.find_text(extensions, "topo:MaxSpeed")
# max_elevation = self.find_text(extensions, "topo:MaxElevation")
# min_elevation = self.find_text(extensions, "topo:MinElevation")
# ascent = self.find_text(extensions, "topo:Ascent")
# descent = self.find_text(extensions, "topo:Descent")
# avg_ascent_rate = self.find_text(extensions, "topo:AvgAscentRate")
# max_ascent_rate = self.find_text(extensions, "topo:MaxAscentRate")
# avg_descent_rate = self.find_text(extensions, "topo:AvgDescentRate")
# max_descent_rate = self.find_text(extensions, "topo:MaxDescentRate")

# return Extensions(tag, display_color, distance, total_elapsed_time, moving_time, stopped_time, moving_speed, max_speed, max_elevation, min_elevation, ascent, descent, avg_ascent_rate, max_ascent_rate, avg_descent_rate, max_descent_rate)
return None

def parse_link(self, link, tag: str ="link") -> Union[Link, None]:
"""
Parse linkType element from GPX file.
Expand All @@ -372,7 +373,7 @@ def parse_link(self, link, tag: str ="link") -> Link:

return Link(tag, href, text, type)

def parse_metadata(self, metadata, tag: str = "metadata") -> Metadata:
def parse_metadata(self, metadata, tag: str = "metadata") -> Union[Metadata, None]:
"""
Parse metadataType element from GPX file.
Expand All @@ -398,7 +399,7 @@ def parse_metadata(self, metadata, tag: str = "metadata") -> Metadata:

return Metadata(tag, name, desc, author, copyright, link, time, keywords, bounds, extensions)

def parse_person(self, person, tag: str ="person") -> Person:
def parse_person(self, person, tag: str ="person") -> Union[Person, None]:
"""
Parse personType element from GPX file.
Expand All @@ -421,7 +422,7 @@ def parse_person(self, person, tag: str ="person") -> Person:
def parse_point_segment(self, point_segment, tag: str = "ptseg") -> PointSegment:
pass

def parse_point(self, point, tag: str = "pt") -> Point:
def parse_point(self, point, tag: str = "pt") -> Union[Point, None]:
"""
Parse ptType element from GPX file.
Expand All @@ -442,7 +443,7 @@ def parse_point(self, point, tag: str = "pt") -> Point:

return Point(tag, lat, lon, ele, time)

def parse_route(self, route, tag: str = "rte") -> Route:
def parse_route(self, route, tag: str = "rte") -> Union[Route, None]:
"""
Parse rteType element from GPX file.
Expand Down Expand Up @@ -472,7 +473,7 @@ def parse_route(self, route, tag: str = "rte") -> Route:

return Route(tag, name, cmt, desc, src, link, number, type, extensions, rtept)

def parse_track_segment(self, track_segment, tag: str = "trkseg") -> TrackSegment:
def parse_track_segment(self, track_segment, tag: str = "trkseg") -> Union[TrackSegment, None]:
"""
Parse trksegType element from GPX file.
Expand All @@ -497,7 +498,7 @@ def parse_track_segment(self, track_segment, tag: str = "trkseg") -> TrackSegmen

return TrackSegment(tag, trkpt, extensions)

def parse_track(self, track, tag: str = "trk") -> Track:
def parse_track(self, track, tag: str = "trk") -> Union[Track, None]:
"""
Parse trkType element from GPX file.
Expand Down Expand Up @@ -527,7 +528,7 @@ def parse_track(self, track, tag: str = "trk") -> Track:

return Track(tag, name, cmt, desc, src, link, number, type, extensions, trkseg)

def parse_way_point(self, way_point, tag: str = "wpt") -> WayPoint:
def parse_way_point(self, way_point, tag: str = "wpt") -> Union[WayPoint, None]:
"""
Parse wptType element from GPX file.
Expand Down Expand Up @@ -566,13 +567,21 @@ def parse_way_point(self, way_point, tag: str = "wpt") -> WayPoint:

return WayPoint(tag, lat, lon, ele, time, mag_var, geo_id_height, name, cmt, desc, src, link, sym, type, fix, sat, hdop, vdop, pdop, age_of_gps_data, dgpsid, extensions)

def find_xmlns_xsi(self) -> Union[str, None]:
schema_location = None
for elmt in list(self.gpx_root.attrib.keys()):
if elmt.endswith("schemaLocation"):
schema_location = elmt[1:-15]
return schema_location

def parse_root_properties(self):
"""
Parse XML properties from GPX file.
"""
self.gpx.creator = self.gpx_root.attrib["creator"]
self.gpx.version = self.gpx_root.attrib["version"]
self.gpx.xmlns = self.gpx_root.tag[1:-4]
self.gpx.xmlns_xsi = self.find_xmlns_xsi()
self.name_space["topo"] = self.gpx.xmlns
name_spaces = self.gpx_root.get("{http://www.w3.org/2001/XMLSchema-instance}schemaLocation").split(" ")
self.gpx.xsi_schema_location = [x for x in name_spaces if x != ""]
Expand Down
2 changes: 2 additions & 0 deletions ezgpx/gpx_writer/writer.py
Original file line number Diff line number Diff line change
Expand Up @@ -416,12 +416,14 @@ def add_properties_garmin(self) -> None:
schema_location_string += " "
schema_location_string = schema_location_string[:len(schema_location_string)-1]
self.gpx_root.set("xsi:schemaLocation", schema_location_string)
self.gpx_root.set("xmlns:xsi", self.gpx.xmlns_xsi)

def add_properties_strava(self) -> None:
"""
Add Strava style properties to the GPX root element.
"""
self.gpx_root.set("creator", self.gpx.creator)
self.gpx_root.set("xmlns:xsi", self.gpx.xmlns_xsi)
schema_location_string = ""
for loc in self.gpx.xsi_schema_location:
schema_location_string += loc
Expand Down
Loading

0 comments on commit 55c74c6

Please sign in to comment.