Skip to content

Commit

Permalink
#1 Work in progress:
Browse files Browse the repository at this point in the history
-GPX standard 1.1
-Improved Parser
-Improved Writer
  • Loading branch information
FABallemand committed Jun 19, 2023
1 parent d161630 commit 991b8ff
Show file tree
Hide file tree
Showing 6 changed files with 351 additions and 71 deletions.
3 changes: 3 additions & 0 deletions .vscode/settings.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
{
"C_Cpp.errorSquiggles": "disabled"
}
14 changes: 13 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -40,4 +40,16 @@ test_gpx.plot(start_stop=True, elevation_color=True)
## 👤 Author
- Fabien ALLEMAND

## 📝 TO DO LIST !!
## 📝 TO DO LIST !!
- Complete gpx
```xml
<gpx
version="1.1 [1] ?"
creator="xsd:string [1] ?">
<metadata> metadataType </metadata> [0..1] ?
<wpt> wptType </wpt> [0..*] ?
<rte> rteType </rte> [0..*] ?
<trk> trkType </trk> [0..*] ?
<extensions> extensionsType </extensions> [0..1] ?
</gpx>
```
1 change: 1 addition & 0 deletions ezgpx/gpx_elements/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
from .gpx import *
from .link import *
from .metadata import *
from .person import *
from .track import *
from .track_segment import *
from .track_point import *
16 changes: 16 additions & 0 deletions ezgpx/gpx_elements/person.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
from .email import Email
from .link import Link

class Person():
"""
Person (person) element in GPX file.
"""

def __init__(
self,
name: str = None,
email: Email = None,
link: Link = None) -> None:
self.name: str = name
self.email: Email = email
self.link: Link = link
301 changes: 250 additions & 51 deletions ezgpx/gpx_parser/parser.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@

import xml.etree.ElementTree as ET

from ..gpx_elements import Bounds, Copyright, Email, Extensions, Gpx, Link, Metadata, TrackPoint, TrackSegment, Track
from ..gpx_elements import Bounds, Copyright, Email, Extensions, Gpx, Link, Metadata, Person, TrackPoint, TrackSegment, Track

class Parser():

Expand All @@ -20,83 +20,282 @@ def __init__(self, file_path: str = ""):
def check_schema(self):
pass

def parse_link(self, link) -> Link:
href = link.get("href")
text = link.find("topo:text", self.name_space)
type = link.find("topo:type", self.name_space)
return Link(href, text, type)
def get_text(self, element, sub_element: str) -> str:
"""
Get text from sub-element.
Args:
element (???): Parsed element from GPX file.
sub_element (str): Sub-element name.
Returns:
str: Text from sub-element.
"""
try:
text = element.get(sub_element).text
except:
logging.DEBUG(f"{element} has no attribute {sub_element}")
text = None
return text

def find_text(self, element, sub_element: str) -> str:
"""
Find text from sub-element.
Args:
element (???): Parsed element from GPX file.
sub_element (str): Sub-element name.
Returns:
str: Text from sub-element.
"""
try:
text = element.find(sub_element, self.name_space).text
except:
logging.debug(f"{element} has no attribute {sub_element}")
text = None
return text

def parse_bounds(self, bounds) -> Bounds:
"""
Parse bounds element in GPX file.
Args:
bounds (???): Parsed bounds element.
Returns:
Bounds: Bounds object.
"""
if bounds is None:
return None

minlat = self.get_text(bounds, "minlat")
minlon = self.get_text(bounds, "minlon")
maxlat = self.get_text(bounds, "maxlat")
maxlon = self.get_text(bounds, "maxlon")
return Bounds(minlat, minlon, maxlat, maxlon)

def parse_copyright(self, copyright) -> Copyright:
author = copyright.get("author")
year = copyright.find("topo:year", self.name_space)
licence = copyright.find("topo:licence", self.name_space)
"""
Parse copyright element in GPX file.
Args:
copyright (???): Parsed copyright element.
Returns:
Copyright: Copyright object.
"""
if copyright is None:
return None

author = self.get_text(copyright, "author")
year = self.find_text(copyright, "topo:year")
licence = self.find_text(copyright, "topo:licence")
return Copyright(author, year, licence)

def parse_email(self, email) -> Email:
id = email.get("id")
domain = email.get("domain")
"""
Parse email element in GPX file.
Args:
email (???): Parsed email element.
Returns:
Email: Email object.
"""
if email is None:
return None

id = self.get_text(email, "id")
domain = self.get_text(email, "domain")
return Email(id, domain)

def parse_bounds(self, bounds) -> Bounds:
minlat = bounds.get("minlat")
minlon = bounds.get("minlon")
maxlat = bounds.get("maxlat")
maxlon = bounds.get("maxlon")
return Bounds(minlat, minlon, maxlat, maxlon)
def parse_extensions(self, extensions) -> Extensions:
"""
Parse extensions element in GPX file.
Args:
extensions (???): Parsed extensions element.
Returns:
Extensions: Extensions object.
"""
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(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) -> Link:
"""
Parse link element in GPX file.
Args:
link (???): Parsed link element.
Returns:
Link: Link object.
"""
if link is None:
return None

href = self.get_text(link, "href")
text = self.find_text(link, "topo:text")
type = self.find_text(link, "topo:type")
return Link(href, text, type)

def parse_person(self, person) -> Person:
"""
Parse person element in GPX file.
Args:
person (???): Parsed person element.
Returns:
Person: Person object.
"""
if person is None:
return None

name = self.find_text(person, "topo:name")
email = self.parse_email(person.find("topo:email", self.name_space))
link = self.parse_link(person.find("topo:link", self.name_space))
return Person(name, email, link)

def parse_metadata(self):
"""
Parse metadata element in GPX File.
"""

metadata = self.gpx_root.find("topo:metadata", self.name_space)

name = metadata.find("topo:name", self.name_space)
desc = metadata.find("topo:desc", self.name_space)
author = metadata.find("topo:author", self.name_space)
name = self.find_text(metadata, "topo:name")
desc = self.find_text(metadata, "topo:desc")
author = self.parse_person(metadata.find("topo:author", self.name_space))
copyright = self.parse_copyright(metadata.find("topo:copyright", self.name_space))
link = self.parse_link(metadata.find("topo:link", self.name_space))
time = metadata.find("topo:time", self.name_space)
keywords = metadata.find("topo:keywords", self.name_space)
time = datetime.strptime(metadata.find("topo:time", self.name_space).text, "%Y-%m-%dT%H:%M:%SZ")
keywords = self.find_text(metadata, "topo:keywords")
bounds = self.parse_bounds(metadata.find("topo:bounds", self.name_space))
extensions = metadata.find("topo:extensions", self.name_space)
extensions = self.parse_extensions(metadata.find("topo:extensions", self.name_space))

self.gpx.metadata = Metadata(name, desc, author, copyright, link, time, keywords, bounds, extensions)

def parse_tracks(self):
def parse_point(self, point) -> TrackPoint:
"""
Parse trkpt element from GPX file.
# Tracks
tracks = self.gpx_root.findall("topo:trk", self.name_space)
for track in tracks:
name = track.find("topo:name", self.name_space)
segments = track.findall("topo:trkseg", self.name_space)
Args:
point (???): Parsed trkpt element.
# Create new track
gpx_track = Track(name=name.text)
Returns:
TrackPoint: TrackPoint object.
"""
try:
lat = float(point.get("lat"))
except:
logging.error(f"{point} contains invalid latitude: {point.get('lat')}")
lat = None

# Track segments
for segment in segments:
points = segment.findall("topo:trkpt", self.name_space)
try:
lon = float(float(point.get("lon")))
except:
logging.error(f"{point} contains invalid longitude: {float(point.get('lon'))}")
lon = None

try:
elevation = float(self.find_text(point, "topo:ele"))
except:
logging.error(f"{point} contains invalid elevation: {self.find_text(point, 'topo:ele')}")
elevation = None
time = datetime.strptime(self.find_text(point, "topo:time"), "%Y-%m-%dT%H:%M:%SZ")

return TrackPoint(lat, lon, elevation, time)

# Crete new segment
gpx_segment = TrackSegment()
def parse_segment(self, segment) -> TrackSegment:
"""
Parse trkseg element from GPX file.
# Track points
for point in points:
elevation = point.find("topo:ele", self.name_space)
time = point.find("topo:time", self.name_space)

# Create new point
gpx_point = TrackPoint(float(point.get("lat")),
float(point.get("lon")),
float(elevation.text),
datetime.strptime(time.text, "%Y-%m-%dT%H:%M:%SZ"))
Args:
segment (???): Parsed trkseg element.
gpx_segment.track_points.append(gpx_point)
Returns:
TrackSegment: TrackSegment object.
"""
# Points
trkpt = []
points = segment.findall("topo:trkpt", self.name_space)
for point in points:
trkpt.append(self.parse_point(point))

gpx_track.track_segments.append(gpx_segment)

self.gpx.tracks.append(gpx_track)
# Extensions
extensions = self.parse_extensions(segment.find("topo:extensions", self.name_space))

return TrackSegment(trkpt, extensions)

def parse_track(self, track) -> Track:
"""
Parse trk element in GPX file.
Args:
track (???): Parsed trk element.
Returns:
Track: Track object.
"""
name = self.find_text(track, "topo:name")
cmt = self.find_text(track, "topo:cmt")
desc = self.find_text(track, "topo:desc")
src = self.find_text(track, "topo:src")
link = self.parse_link(track.find("topo:link", self.name_space))
try:
number = int(self.find_text(track, "topo:number"))
except:
logging.error(f"{track} contains invalid number: {self.find_text(track, 'topo:number')}")
number = None
type = self.find_text(track, "topo:type")
extensions = self.parse_extensions(track.find("topo:extensions", self.name_space))

trkseg = []
segments = track.findall("topo:trkseg", self.name_space)
for segment in segments:
trkseg.append(self.parse_segment(segment))

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

def parse_tracks(self):
"""
Parse track elements in GPX file.
"""
# Tracks
tracks = self.gpx_root.findall("topo:trk", self.name_space)
for track in tracks:
self.gpx.tracks.append(self.parse_track(track))

def parse(self, file_path: str = "") -> Gpx:

"""
Parse GPX file.
Args:
file_path (str, optional): Path to the file to parse. Defaults to "".
Returns:
Gpx: Gpx object., self.name_space).text
"""
# File
if file_path != "":
self.file_path = file_path
Expand Down
Loading

0 comments on commit 991b8ff

Please sign in to comment.