diff --git a/ezgpx/gpx/gpx.py b/ezgpx/gpx/gpx.py index b223da9..879bbf6 100644 --- a/ezgpx/gpx/gpx.py +++ b/ezgpx/gpx/gpx.py @@ -65,8 +65,8 @@ def __init__( self.gpx: Gpx = None self._ele_data: bool = False self._time_data: bool = False - self.precisions: Dict = None - self.time_format: str = None + self._precisions: Dict = None + self._time_format: str = None # Parsers self._gpx_parser: GPXParser = None @@ -87,16 +87,15 @@ def __init__( self.gpx = self._gpx_parser.gpx self._ele_data = self._gpx_parser.ele_data self._time_data = self._gpx_parser.time_data - self.precisions = self._gpx_parser.precisions - self.time_format = self._gpx_parser.time_format - self.extensions_fields = self._gpx_parser.extensions_fields + self._precisions = self._gpx_parser.precisions + self._time_format = self._gpx_parser.time_format # KML elif file_path.endswith(".kml"): self._kml_parser = KMLParser(file_path, xml_schema, xml_extensions_schemas) self.gpx = self._kml_parser.gpx - self.precisions = self._kml_parser.precisions - self.time_format = self._kml_parser.time_format + self._precisions = self._kml_parser.precisions + self._time_format = self._kml_parser.time_format # KMZ elif file_path.endswith(".kmz"): @@ -109,16 +108,16 @@ def __init__( self._write_tmp_kml("tmp.kml", kml) self._kml_parser = KMLParser("tmp.kml", xml_schema, xml_extensions_schemas) self.gpx = self._kml_parser.gpx - self.precisions = self._kml_parser.precisions - self.time_format = self._kml_parser.time_format + self._precisions = self._kml_parser.precisions + self._time_format = self._kml_parser.time_format os.remove("tmp.kml") # FIT elif file_path.endswith(".fit"): self._fit_parser = FitParser(file_path) self.gpx = self._fit_parser.gpx - self.precisions = self._fit_parser.precisions - self.time_format = self._fit_parser.time_format + self._precisions = self._fit_parser.precisions + self._time_format = self._fit_parser.time_format # NOT SUPPORTED else: @@ -126,10 +125,10 @@ def __init__( "Consider renaming your file with the proper file extension.") self._gpx_writer: GPXWriter = GPXWriter(self.gpx, - precisions=self.precisions, time_format=self.time_format, - extensions_fields=self.extensions_fields) + precisions=self._precisions, time_format=self._time_format, + extensions_fields=self._gpx_parser.extensions_fields if self._gpx_parser else {}) self._kml_writer: KMLWriter = KMLWriter(self.gpx, - precisions=self.precisions, time_format=self.time_format) + precisions=self._precisions, time_format=self._time_format) # Invalid file path else: diff --git a/ezgpx/gpx_elements/gpx.py b/ezgpx/gpx_elements/gpx.py index 52e1d75..3a9a25f 100644 --- a/ezgpx/gpx_elements/gpx.py +++ b/ezgpx/gpx_elements/gpx.py @@ -2,7 +2,7 @@ from importlib.resources import files except ImportError: from importlib_resources import files -from typing import Union, List, Tuple +from typing import Union, List, Dict, Tuple import logging import xmlschema import pandas as pd @@ -25,71 +25,27 @@ class Gpx(): def __init__( self, tag: str = "gpx", - creator: str = None, - xmlns: str = None, version: str = None, - xmlns_xsi: str = None, + creator: str = None, xsi_schema_location: List[str] = None, - xmlns_gpxtpx: str = None, - xmlns_gpxx: str = None, - xmlns_gpxtrk: str = None, - xmlns_wptx1: str = None, + xmlns: Dict = None, metadata: Metadata = None, wpt: List[WayPoint] = None, rte: List[Route] = None, trk: List[Track] = None, extensions: Extensions = None) -> None: - """ - Initialize Gpx instance. - - Parameters - ---------- - tag : str, optional - XML tag, by default "gpx" - creator : str, optional - Creator, by default None - xmlns : str, optional - XML xmlns, by default None - version : str, optional - Version, by default None - xmlns_xsi : str, optional - XML xmlns_xsi, by default None - xsi_schema_location : List[str], optional - XML schema location, by default None - xmlns_gpxtpx : str, optional - ???, by default None - xmlns_gpxx : str, optional - ???, by default None - xmlns_gpxtrk : str, optional - ???, by default None - xmlns_wptx1 : str, optional - ???, by default None - metadata : Metadata, optional - Metadata, by default None - wpt : List[WayPoint], optional - Way points, by default None - rte : List[Route], optional - Routes, by default None - trk : List[Track], optional - List of tracks, by default None - extensions : Extensions, optional - Extensions, by default None - """ + self.tag: str = tag - self.creator: str = creator - self.xmlns: str = xmlns self.version: str = version - - self.xmlns_xsi: str = xmlns_xsi + self.creator: str = creator if xsi_schema_location is None: - self.xsi_schema_location: str = [] + self.xsi_schema_location: List[str] = [] else: - self.xsi_schema_location: str = xsi_schema_location - self.xmlns_gpxtpx: str = xmlns_gpxtpx - self.xmlns_gpxx: str = xmlns_gpxx - self.xmlns_gpxtrk: str = xmlns_gpxtrk - self.xmlns_wptx1: str = xmlns_wptx1 - + self.xsi_schema_location: List[str] = xsi_schema_location + if xmlns is None: + self.xmlns: Dict = {} + else: + self.xmlns: Dict = xmlns self.metadata: Metadata = metadata if wpt is None: self.wpt: List[WayPoint] = [] diff --git a/ezgpx/parser/gpx_parser/gpx_parser.py b/ezgpx/parser/gpx_parser/gpx_parser.py index 8b2ed86..04fc179 100644 --- a/ezgpx/parser/gpx_parser/gpx_parser.py +++ b/ezgpx/parser/gpx_parser/gpx_parser.py @@ -3,7 +3,6 @@ import logging from datetime import datetime import xml.etree.ElementTree as ET -import re from ..xml_parser import XMLParser from ...gpx_elements import Bounds, Copyright, Email, Extensions, Gpx, Link, Metadata, Person, Point, PointSegment, Route, TrackSegment, Track, WayPoint @@ -32,10 +31,6 @@ def __init__( super().__init__(file_path, check_xml_schemas, xml_extensions_schemas) - - self.ele_data: bool = False - self.time_data:bool = False - self.extensions_fields: dict = {} if self.file_path is not None and os.path.exists(self.file_path): self.parse() @@ -48,11 +43,11 @@ def find_precisions(self): Also find if the GPX file contains elevation data. """ # Point - track = self.xml_root.findall("trk", self.name_space)[0] - segment = track.findall("trkseg", self.name_space)[0] - point = segment.findall("trkpt", self.name_space)[0] + track = self.xml_root.findall("trk", self.name_spaces)[0] + segment = track.findall("trkseg", self.name_spaces)[0] + point = segment.findall("trkpt", self.name_spaces)[0] - ele_text = point.findtext("ele", namespaces=self.name_space) + ele_text = point.findtext("ele", namespaces=self.name_spaces) if ele_text is not None: self.ele_data = True else: @@ -69,16 +64,16 @@ def find_time_element(self) -> Union[str, None]: Union[str, None]: Time element. """ # Use time from metadata - metadata = self.xml_root.find("metadata", self.name_space) - time = metadata.findtext("time", namespaces=self.name_space) + metadata = self.xml_root.find("metadata", self.name_spaces) + time = metadata.findtext("time", namespaces=self.name_spaces) if time is not None: return time # Use time from track point - track = self.xml_root.findall("trk", self.name_space)[0] # Optimise, load only once?? - segment = track.findall("trkseg", self.name_space)[0] - point = segment.findall("trkpt", self.name_space)[0] - time = point.findtext("time", namespaces=self.name_space) + track = self.xml_root.findall("trk", self.name_spaces)[0] # Optimise, load only once?? + segment = track.findall("trkseg", self.name_spaces)[0] + point = segment.findall("trkpt", self.name_spaces)[0] + time = point.findtext("time", namespaces=self.name_spaces) if time is not None: return time @@ -139,8 +134,8 @@ def parse_copyright(self, copyright, tag: str ="copyright") -> Union[Copyright, return None author = copyright.get("author") - year = copyright.findtext("year", namespaces=self.name_space) - licence = copyright.findtext("licence", namespaces=self.name_space) + year = copyright.findtext("year", namespaces=self.name_spaces) + licence = copyright.findtext("licence", namespaces=self.name_spaces) return Copyright(tag, author, year, licence) @@ -177,12 +172,25 @@ def parse_extensions(self, extensions, element_type: str, tag: str ="extensions" if extensions is None: return None - values = dict([(elmt.tag, elmt.text) for elmt in extensions.iter() if re.match(r"[^\s]", elmt.text)]) + def construct_dict(e0): + e1s = [e1 for e1 in e0.iter()][1:] + if len(e1s) > 0: + d = {"attrib": dict(e0.items()), + "elmts": {}} + for e1 in e1s: + d["elmts"][e1.tag] = construct_dict(e1) + return d + else: + return {"attrib": {}, + "elmts": e0.text} + + ext = [e for e in extensions.iter()][1] + values = {ext.tag: {}} + values[ext.tag] = construct_dict(ext) + # Etensions fields are based on the first occurance of a type encountered in the file if self.extensions_fields.get(element_type) is None: - self.extensions_fields[element_type] = list(values.keys()) - else: - self.extensions_fields[element_type] = list(set(self.extensions_fields[element_type]) | set(list(values.keys()))) + self.extensions_fields[element_type] = values return Extensions(tag, values) @@ -201,8 +209,8 @@ def parse_link(self, link, tag: str ="link") -> Union[Link, None]: return None href = link.get("href") - text = link.findtext("text", namespaces=self.name_space) - type = link.findtext("type", namespaces=self.name_space) + text = link.findtext("text", namespaces=self.name_spaces) + type = link.findtext("type", namespaces=self.name_spaces) return Link(tag, href, text, type) @@ -220,15 +228,15 @@ def parse_metadata(self, metadata, tag: str = "metadata") -> Union[Metadata, Non if metadata is None: return None - name = metadata.findtext("name", namespaces=self.name_space) - desc = metadata.findtext("desc", namespaces=self.name_space) - author = self.parse_person(metadata.find("author", self.name_space)) - copyright = self.parse_copyright(metadata.find("copyright", self.name_space)) - link = self.parse_link(metadata.find("link", self.name_space)) + name = metadata.findtext("name", namespaces=self.name_spaces) + desc = metadata.findtext("desc", namespaces=self.name_spaces) + author = self.parse_person(metadata.find("author", self.name_spaces)) + copyright = self.parse_copyright(metadata.find("copyright", self.name_spaces)) + link = self.parse_link(metadata.find("link", self.name_spaces)) time = self.find_time(metadata, "time") - keywords = metadata.findtext("keywords", namespaces=self.name_space) - bounds = self.parse_bounds(metadata.find("bounds", self.name_space)) - extensions = self.parse_extensions(metadata.find("extensions", self.name_space), tag) + keywords = metadata.findtext("keywords", namespaces=self.name_spaces) + bounds = self.parse_bounds(metadata.find("bounds", self.name_spaces)) + extensions = self.parse_extensions(metadata.find("extensions", self.name_spaces), tag) return Metadata(tag, name, desc, author, copyright, link, time, keywords, bounds, extensions) @@ -246,9 +254,9 @@ def parse_person(self, person, tag: str ="person") -> Union[Person, None]: if person is None: return None - name = person.findtext("name", namespaces=self.name_space) - email = self.parse_email(person.find("email", self.name_space)) - link = self.parse_link(person.find("link", self.name_space)) + name = person.findtext("name", namespaces=self.name_spaces) + email = self.parse_email(person.find("email", self.name_spaces)) + link = self.parse_link(person.find("link", self.name_spaces)) return Person(tag, name, email, link) @@ -310,15 +318,15 @@ def parse_route(self, route, tag: str = "rte") -> Union[Route, None]: if route is None: return None - name = route.findtext("name", namespaces=self.name_space) - cmt = route.findtext("cmt", namespaces=self.name_space) - desc = route.findtext("desc", namespaces=self.name_space) - src = route.findtext("src", namespaces=self.name_space) - link = self.parse_link(route.find("link", self.name_space)) + name = route.findtext("name", namespaces=self.name_spaces) + cmt = route.findtext("cmt", namespaces=self.name_spaces) + desc = route.findtext("desc", namespaces=self.name_spaces) + src = route.findtext("src", namespaces=self.name_spaces) + link = self.parse_link(route.find("link", self.name_spaces)) number = self.find_int(route, "number") - type = route.findtext("type", namespaces=self.name_space) - extensions = self.parse_extensions(route.find("extensions", self.name_space), tag) - rtept = [self.parse_way_point(way_point) for way_point in route.findall("rtept", self.name_space)] + type = route.findtext("type", namespaces=self.name_spaces) + extensions = self.parse_extensions(route.find("extensions", self.name_spaces), tag) + rtept = [self.parse_way_point(way_point) for way_point in route.findall("rtept", self.name_spaces)] return Route(tag, name, cmt, desc, src, link, number, type, extensions, rtept) @@ -336,8 +344,8 @@ def parse_track_segment(self, track_segment, tag: str = "trkseg") -> Union[Track if track_segment is None: return None - trkpt = [self.parse_way_point(track_point, "trkpt") for track_point in track_segment.findall("trkpt", self.name_space)] - extensions = self.parse_extensions(track_segment.find("extensions", self.name_space), tag) + trkpt = [self.parse_way_point(track_point, "trkpt") for track_point in track_segment.findall("trkpt", self.name_spaces)] + extensions = self.parse_extensions(track_segment.find("extensions", self.name_spaces), tag) return TrackSegment(tag, trkpt, extensions) @@ -355,15 +363,15 @@ def parse_track(self, track, tag: str = "trk") -> Union[Track, None]: if track is None: return None - name = track.findtext("name", namespaces=self.name_space) - cmt = track.findtext("cmt", namespaces=self.name_space) - desc = track.findtext("desc", namespaces=self.name_space) - src = track.findtext("src", namespaces=self.name_space) - link = self.parse_link(track.find("link", self.name_space)) + name = track.findtext("name", namespaces=self.name_spaces) + cmt = track.findtext("cmt", namespaces=self.name_spaces) + desc = track.findtext("desc", namespaces=self.name_spaces) + src = track.findtext("src", namespaces=self.name_spaces) + link = self.parse_link(track.find("link", self.name_spaces)) number = self.find_int(track, "number") - type = track.findtext("type", namespaces=self.name_space) - extensions = self.parse_extensions(track.find("extensions", self.name_space), tag) - trkseg = [self.parse_track_segment(segment) for segment in track.findall("trkseg", self.name_space)] + type = track.findtext("type", namespaces=self.name_spaces) + extensions = self.parse_extensions(track.find("extensions", self.name_spaces), tag) + trkseg = [self.parse_track_segment(segment) for segment in track.findall("trkseg", self.name_spaces)] return Track(tag, name, cmt, desc, src, link, number, type, extensions, trkseg) @@ -388,21 +396,21 @@ def parse_way_point(self, way_point, tag: str = "wpt") -> Union[WayPoint, None]: mag_var = self.find_float(way_point, "magvar") geo_id_height = self.find_float(way_point, "geoidheight") geo_id_height = self.find_float(way_point, "geoidheight") - name = way_point.findtext("name", namespaces=self.name_space) - cmt = way_point.findtext("cmt", namespaces=self.name_space) - desc = way_point.findtext("desc", namespaces=self.name_space) - src = way_point.findtext("src", namespaces=self.name_space) - link = self.parse_link(way_point.find("link", self.name_space)) - sym = way_point.findtext("sym", namespaces=self.name_space) - type = way_point.findtext("type", namespaces=self.name_space) - fix = way_point.findtext("fix", namespaces=self.name_space) + name = way_point.findtext("name", namespaces=self.name_spaces) + cmt = way_point.findtext("cmt", namespaces=self.name_spaces) + desc = way_point.findtext("desc", namespaces=self.name_spaces) + src = way_point.findtext("src", namespaces=self.name_spaces) + link = self.parse_link(way_point.find("link", self.name_spaces)) + sym = way_point.findtext("sym", namespaces=self.name_spaces) + type = way_point.findtext("type", namespaces=self.name_spaces) + fix = way_point.findtext("fix", namespaces=self.name_spaces) sat = self.find_int(way_point, "sat") hdop = self.find_float(way_point, "hdop") vdop = self.find_float(way_point, "vdop") pdop = self.find_float(way_point, "pdop") age_of_gps_data = self.find_float(way_point, "ageofgpsdata") dgpsid = self.find_float(way_point, "dgpsid") - extensions = self.parse_extensions(way_point.find("extensions", self.name_space), tag) + extensions = self.parse_extensions(way_point.find("extensions", self.name_spaces), tag) 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) @@ -412,18 +420,20 @@ def parse_root_properties(self): """ self.gpx.creator = self.xml_root.attrib["creator"] self.gpx.version = self.xml_root.attrib["version"] + schema_location = self.xml_root.get("{http://www.w3.org/2001/XMLSchema-instance}schemaLocation").split(" ") + self.gpx.xsi_schema_location = [x for x in schema_location if x != ""] def parse_root_metadata(self): """ Parse metadataType elements from GPX file. """ - self.gpx.metadata = self.parse_metadata(self.xml_root.find("metadata", self.name_space)) + self.gpx.metadata = self.parse_metadata(self.xml_root.find("metadata", self.name_spaces)) def parse_root_way_points(self): """ Parse wptType elements from GPX file. """ - way_points = self.xml_root.findall("wpt", self.name_space) + way_points = self.xml_root.findall("wpt", self.name_spaces) for way_point in way_points: self.gpx.wpt.append(self.parse_way_point(way_point)) @@ -431,7 +441,7 @@ def parse_root_routes(self): """ Parse rteType elements from GPX file """ - routes = self.xml_root.findall("rte", self.name_space) + routes = self.xml_root.findall("rte", self.name_spaces) for route in routes: self.gpx.rte.append(self.parse_route(route)) @@ -439,7 +449,7 @@ def parse_root_tracks(self): """ Parse trkType elements from GPX file. """ - tracks = self.xml_root.findall("trk", self.name_space) + tracks = self.xml_root.findall("trk", self.name_spaces) for track in tracks: self.gpx.trk.append(self.parse_track(track)) @@ -447,7 +457,7 @@ def parse_root_extensions(self): """ Parse extensionsType elements from GPX file. """ - extensions = self.xml_root.find("extensions", self.name_space) + extensions = self.xml_root.find("extensions", self.name_spaces) self.gpx.extensions = self.parse_extensions(extensions, "gpx") def parse(self) -> Gpx: diff --git a/ezgpx/parser/parser.py b/ezgpx/parser/parser.py index c53db6e..9e1b653 100644 --- a/ezgpx/parser/parser.py +++ b/ezgpx/parser/parser.py @@ -1,4 +1,4 @@ -from typing import Optional, Union +from typing import Dict, Optional, Union import logging from ..gpx_elements import Gpx @@ -11,7 +11,10 @@ class Parser(): File parser. """ - def __init__(self, file_path: Optional[str] = None) -> None: + def __init__( + self, + file_path: Optional[str] = None, + name_spaces: Dict = None) -> None: """ Initialize Parser instance. @@ -20,6 +23,8 @@ def __init__(self, file_path: Optional[str] = None) -> None: """ self.file_path: str = file_path + self.ele_data: bool = False + self.time_data:bool = False self.precisions: dict = { "lat_lon": DEFAULT_PRECISION, "elevation": DEFAULT_PRECISION, @@ -29,10 +34,9 @@ def __init__(self, file_path: Optional[str] = None) -> None: "rate": DEFAULT_PRECISION, "default": DEFAULT_PRECISION } - self.time_data: bool = False self.time_format: str = DEFAULT_TIME_FORMAT - self.gpx: Gpx = Gpx() + self.gpx: Gpx = Gpx(xmlns=name_spaces) def find_precision(self, number: Union[int, float, str]) -> int: """ diff --git a/ezgpx/parser/xml_parser/xml_parser.py b/ezgpx/parser/xml_parser/xml_parser.py index d200796..dc8c38c 100644 --- a/ezgpx/parser/xml_parser/xml_parser.py +++ b/ezgpx/parser/xml_parser/xml_parser.py @@ -1,4 +1,4 @@ -from typing import Optional, Union +from typing import Dict, Optional, Union import logging from datetime import datetime import xml.etree.ElementTree as ET @@ -29,9 +29,10 @@ def __init__( Requires internet connection and is not guaranted to work, by default False """ - super().__init__(file_path) - - self.name_space: dict = dict([node for _, node in ET.iterparse(file_path, events=["start-ns"])]) + self.name_spaces: dict = dict([node for _, node in ET.iterparse(file_path, events=["start-ns"])]) + self.extensions_fields: Dict = {} + + super().__init__(file_path, self.name_spaces) self.xml_schema: bool = xml_schema self.xml_extensions_schemas: bool = xml_extensions_schemas diff --git a/ezgpx/writer/gpx_writer/gpx_writer.py b/ezgpx/writer/gpx_writer/gpx_writer.py index 7795fd0..404dc5d 100644 --- a/ezgpx/writer/gpx_writer/gpx_writer.py +++ b/ezgpx/writer/gpx_writer/gpx_writer.py @@ -50,13 +50,15 @@ def placeholder_behavior(element, subelement): self._add_track_segment = placeholder_behavior self._add_track = placeholder_behavior self._add_way_point = placeholder_behavior + self._add_trkpt_point = placeholder_behavior - self._add_gpx_extensions = placeholder_behavior - self._add_metadata_extensions = placeholder_behavior - self._add_wpt_extensions = placeholder_behavior - self._add_rte_extensions = placeholder_behavior - self._add_trk_extensions = placeholder_behavior - self._add_trkseg_extensions = placeholder_behavior + # self._add_gpx_extensions = placeholder_behavior + # self._add_metadata_extensions = placeholder_behavior + # self._add_wpt_extensions = placeholder_behavior + # self._add_rte_extensions = placeholder_behavior + # self._add_trk_extensions = placeholder_behavior + # self._add_trkseg_extensions = placeholder_behavior + # self._add_trkpt_extensions = placeholder_behavior # Parameters self.precisions: Dict = precisions @@ -106,6 +108,40 @@ def add_email(self, element: ET.Element, email: Email) -> ET.Element: """ return self._add_email(self, element, email) + def _add_extensions_rec(self, extensions_, values, extensions_fields) -> ET.Element: + if extensions_ is not None: + # Add n-th level extensions + for k0, v0 in values.items(): + if k0 in extensions_fields.keys(): + # If k0 contains sub-elements + if isinstance(v0["elmts"], dict): + # Create sub-element + sub_extensions_ = ET.SubElement(extensions_, k0) + + # Set attributes + for k1, v1 in v0["attrib"].items(): + if k1 in extensions_fields[k0]["attrib"].keys(): + self.setIfNotNone(extensions_, k1, v1) + + # Add (n+1)-th level extensions + sub_extensions_ = self._add_extensions_rec(sub_extensions_, v0["elmts"], extensions_fields[k0]["elmts"]) + # Else, k0 contains a value + else: + extensions_, sub_extensions_ = self.add_subelement(extensions_, k0, v0["elmts"]) + + # Set attributes + for k1, v1 in v0["attrib"].items(): + if k1 in extensions_fields[k0]["attrib"].keys(): + self.setIfNotNone(sub_extensions_, k1, v1) + + return extensions_ + + def add_extensions(self, element: ET.Element, extensions: Extensions, extensions_fields: Dict) -> ET.Element: + if extensions is not None: + extensions_ = ET.SubElement(element, extensions.tag) + extensions_ = self._add_extensions_rec(extensions_, extensions.values, extensions_fields) + return element + def add_link(self, element: ET.Element, link: Link) -> ET.Element: """ Add Link instance element to GPX element. @@ -223,83 +259,96 @@ def add_way_point(self, element: ET.Element, way_point: WayPoint) -> ET.Element: """ return self._add_way_point(self, element, way_point) - def add_gpx_extensions(self, element: ET.Element, extensions: Extensions) -> ET.Element: + def add_track_point(self, element: ET.Element, way_point: WayPoint) -> ET.Element: """ - Add Extensions instance element to gpx GPX element. + Add WayPoint instance (with trkpt tag) element to GPX element. Args: element (xml.etree.ElementTree.Element): GPX element. - extensions (Extensions): Extensions instance to add. + way_point (WayPoint): WayPoint instance to add. Returns: xml.etree.ElementTree.Element: GPX element. """ - return self._add_gpx_extensions(self, element, extensions) + return self._add_track_point(self, element, way_point) - def add_metadata_extensions(self, element: ET.Element, extensions: Extensions) -> ET.Element: - """ - Add Extensions instance element to metadata GPX element. - - Args: - element (xml.etree.ElementTree.Element): GPX element. - extensions (Extensions): Extensions instance to add. - - Returns: - xml.etree.ElementTree.Element: GPX element. - """ - return self._add_metadata_extensions(self, element, extensions) + # def add_gpx_extensions(self, element: ET.Element, extensions: Extensions) -> ET.Element: + # """ + # Add Extensions instance element to gpx GPX element. + + # Args: + # element (xml.etree.ElementTree.Element): GPX element. + # extensions (Extensions): Extensions instance to add. + + # Returns: + # xml.etree.ElementTree.Element: GPX element. + # """ + # return self._add_gpx_extensions(self, element, extensions) - def add_wpt_extensions(self, element: ET.Element, extensions: Extensions) -> ET.Element: - """ - Add Extensions instance element to wpt GPX element. - - Args: - element (xml.etree.ElementTree.Element): GPX element. - extensions (Extensions): Extensions instance to add. - - Returns: - xml.etree.ElementTree.Element: GPX element. - """ - return self._add_wpt_extensions(self, element, extensions) + # def add_metadata_extensions(self, element: ET.Element, extensions: Extensions) -> ET.Element: + # """ + # Add Extensions instance element to metadata GPX element. + + # Args: + # element (xml.etree.ElementTree.Element): GPX element. + # extensions (Extensions): Extensions instance to add. + + # Returns: + # xml.etree.ElementTree.Element: GPX element. + # """ + # return self._add_metadata_extensions(self, element, extensions) - def add_rte_extensions(self, element: ET.Element, extensions: Extensions) -> ET.Element: - """ - Add Extensions instance element to rte GPX element. - - Args: - element (xml.etree.ElementTree.Element): GPX element. - extensions (Extensions): Extensions instance to add. - - Returns: - xml.etree.ElementTree.Element: GPX element. - """ - return self._add_rte_extensions(self, element, extensions) + # def add_wpt_extensions(self, element: ET.Element, extensions: Extensions) -> ET.Element: + # """ + # Add Extensions instance element to wpt GPX element. + + # Args: + # element (xml.etree.ElementTree.Element): GPX element. + # extensions (Extensions): Extensions instance to add. + + # Returns: + # xml.etree.ElementTree.Element: GPX element. + # """ + # return self._add_wpt_extensions(self, element, extensions) - def add_trk_extensions(self, element: ET.Element, extensions: Extensions) -> ET.Element: - """ - Add Extensions instance element to trk GPX element. - - Args: - element (xml.etree.ElementTree.Element): GPX element. - extensions (Extensions): Extensions instance to add. - - Returns: - xml.etree.ElementTree.Element: GPX element. - """ - return self._add_trk_extensions(self, element, extensions) + # def add_rte_extensions(self, element: ET.Element, extensions: Extensions) -> ET.Element: + # """ + # Add Extensions instance element to rte GPX element. + + # Args: + # element (xml.etree.ElementTree.Element): GPX element. + # extensions (Extensions): Extensions instance to add. + + # Returns: + # xml.etree.ElementTree.Element: GPX element. + # """ + # return self._add_rte_extensions(self, element, extensions) - def add_trkseg_extensions(self, element: ET.Element, extensions: Extensions) -> ET.Element: - """ - Add Extensions instance element to trkseg GPX element. + # def add_trk_extensions(self, element: ET.Element, extensions: Extensions) -> ET.Element: + # """ + # Add Extensions instance element to trk GPX element. + + # Args: + # element (xml.etree.ElementTree.Element): GPX element. + # extensions (Extensions): Extensions instance to add. + + # Returns: + # xml.etree.ElementTree.Element: GPX element. + # """ + # return self._add_trk_extensions(self, element, extensions) + + # def add_trkseg_extensions(self, element: ET.Element, extensions: Extensions) -> ET.Element: + # """ + # Add Extensions instance element to trkseg GPX element. - Args: - element (xml.etree.ElementTree.Element): GPX element. - extensions (Extensions): Extensions instance to add. + # Args: + # element (xml.etree.ElementTree.Element): GPX element. + # extensions (Extensions): Extensions instance to add. - Returns: - xml.etree.ElementTree.Element: GPX element. - """ - return self._add_trkseg_extensions(self, element, extensions) + # Returns: + # xml.etree.ElementTree.Element: GPX element. + # """ + # return self._add_trkseg_extensions(self, element, extensions) def createSchemaLocationString(self, xsi_schema_location: list[str]): """ @@ -321,39 +370,17 @@ def createSchemaLocationString(self, xsi_schema_location: list[str]): schema_location_string += " " schema_location_string = schema_location_string[:-1] return schema_location_string - - def add_properties_garmin(self) -> None: - """ - Add Garmin style properties to the GPX root element. - """ - self.setIfNotNone(self.gpx_root, "xmlns", self.gpx.xmlns) - self.setIfNotNone(self.gpx_root, "creator", self.gpx.creator) - self.setIfNotNone(self.gpx_root, "version", self.gpx.version) - schema_location_string = self.createSchemaLocationString(self.gpx.xsi_schema_location) - self.setIfNotNone(self.gpx_root, "xsi:schemaLocation", schema_location_string) - self.setIfNotNone(self.gpx_root, "xmlns:xsi", self.gpx.xmlns_xsi) - - def add_properties_strava(self) -> None: - """ - Add Strava style properties to the GPX root element. - """ - self.setIfNotNone(self.gpx_root, "creator", self.gpx.creator) - self.setIfNotNone(self.gpx_root, "xmlns:xsi", self.gpx.xmlns_xsi) - schema_location_string = self.createSchemaLocationString(self.gpx.xsi_schema_location) - self.setIfNotNone(self.gpx_root, "xsi:schemaLocation", schema_location_string) - self.setIfNotNone(self.gpx_root, "version", self.gpx.version) - self.setIfNotNone(self.gpx_root, "xmlns", self.gpx.xmlns) def add_root_properties(self) -> None: """ Add properties to the GPX root element. """ - if self.gpx.creator in ["eTrex 32x"]: - self.add_properties_garmin() - elif self.gpx.creator in ["StravaGPX"]: - self.add_properties_strava() - else: - self.add_properties_garmin() # Default to Garmin style + self.setIfNotNone(self.gpx_root, "version", self.gpx.version) + self.setIfNotNone(self.gpx_root, "creator", "ezGPX") + for k, v in self.gpx.xmlns.items(): + self.setIfNotNone(self.gpx_root, "xmlns:" + k if k != "" else "xmlns", v) + schema_location_string = self.createSchemaLocationString(self.gpx.xsi_schema_location) + self.setIfNotNone(self.gpx_root, "xsi:schemaLocation", schema_location_string) def add_root_metadata(self) -> None: """ @@ -389,7 +416,7 @@ def add_root_extensions(self) -> None: Add extensions element to the GPX root element. """ if self.gpx.extensions is not None: - self.gpx_root = self.add_extensions(self.gpx_root, self.gpx.extensions) + self.gpx_root = self.add_extensions(self.gpx_root, self.gpx.extensions, self.extensions_fields.get("gpx")) def gpx_to_string(self) -> str: """ @@ -471,6 +498,7 @@ def write( track_segment_fields: List[str] = TrackSegment.fields, track_fields: List[str] = Track.fields, way_point_fields: List[str] = WayPoint.fields, + track_point_fields: List[str] = WayPoint.fields, xml_schema: bool = False, xml_extensions_schemas: bool = False) -> bool: """ @@ -503,7 +531,8 @@ def write( self.bound_fields = bounds_fields self.copyright_fields = copyright_fields self.email_fields = email_fields - self.extensions_fields_ = extensions_fields # TODO later, ability to personalise fields + if isinstance(extensions_fields, dict): + self.extensions_fields_ = extensions_fields # TODO, remove trailing underscore self.gpx_fields = gpx_fields self.link_fields = link_fields self.metadata_fields = metadata_fields @@ -514,6 +543,7 @@ def write( self.track_segment_fields = track_segment_fields self.track_fields = track_fields self.way_point_fields = way_point_fields + self.track_point_fields = track_point_fields # Correct parameters if "lat" not in self.point_fields: @@ -546,13 +576,15 @@ def write( self._add_track_segment = self.behavior_creator.add_track_segment_creator(self.track_segment_fields) self._add_track = self.behavior_creator.add_track_creator(self.track_fields) self._add_way_point = self.behavior_creator.add_way_point_creator(self.way_point_fields) - - self._add_gpx_extensions = self.behavior_creator.add_extensions_creator(self.extensions_fields.get("gpx")) - self._add_metadata_extensions = self.behavior_creator.add_extensions_creator(self.extensions_fields.get("metadata")) - self._add_wpt_extensions = self.behavior_creator.add_extensions_creator(self.extensions_fields.get("wpt")) - self._add_rte_extensions = self.behavior_creator.add_extensions_creator(self.extensions_fields.get("rte")) - self._add_trk_extensions = self.behavior_creator.add_extensions_creator(self.extensions_fields.get("trk")) - self._add_trkseg_extensions = self.behavior_creator.add_extensions_creator(self.extensions_fields.get("trkseg")) + self._add_track_point = self.behavior_creator.add_track_point_creator(self.track_point_fields) + + # self._add_gpx_extensions = self.behavior_creator.add_extensions_creator(self.extensions_fields.get("gpx")) + # self._add_metadata_extensions = self.behavior_creator.add_extensions_creator(self.extensions_fields.get("metadata")) + # self._add_wpt_extensions = self.behavior_creator.add_extensions_creator(self.extensions_fields.get("wpt")) + # self._add_rte_extensions = self.behavior_creator.add_extensions_creator(self.extensions_fields.get("rte")) + # self._add_trk_extensions = self.behavior_creator.add_extensions_creator(self.extensions_fields.get("trk")) + # self._add_trkseg_extensions = self.behavior_creator.add_extensions_creator(self.extensions_fields.get("trkseg")) + # self._add_trkpt_extensions = self.behavior_creator.add_extensions_creator(self.extensions_fields.get("trkpt")) # Write .gpx file self.gpx_to_string() diff --git a/ezgpx/writer/gpx_writer/gpx_writer_method_behavior_creator.py b/ezgpx/writer/gpx_writer/gpx_writer_method_behavior_creator.py index cdc3ad0..5b53cb6 100644 --- a/ezgpx/writer/gpx_writer/gpx_writer_method_behavior_creator.py +++ b/ezgpx/writer/gpx_writer/gpx_writer_method_behavior_creator.py @@ -53,17 +53,17 @@ def add_email_creator(self, email_fields): func = FunctionType(compiled_code.co_consts[0], globals(), "_add_email") return func - def add_extensions_creator(self, extensions_fields): - code = ('def _add_extensions(writer, element, extensions):' - '\n\tif extensions is not None:' - '\n\t\textensions_ = ET.SubElement(element, extensions.tag)') - if extensions_fields is not None: - for field in extensions_fields: - code += f'\n\t\textensions_, _ = writer.add_subelement(extensions_, "{field}", extensions.values["{field}"])' - code += '\n\treturn element' - compiled_code = compile(code, "<_add_extensions>", "exec") - func = FunctionType(compiled_code.co_consts[0], globals(), "_add_extensions") - return func + # def add_extensions_creator(self, extensions_fields): + # code = ('def _add_extensions(writer, element, extensions):' + # '\n\tif extensions is not None:' + # '\n\t\textensions_ = ET.SubElement(element, extensions.tag)') + # if extensions_fields is not None: + # for k, v in extensions_fields: + # code += f'\n\t\textensions_, _ = writer.add_extensions_element(extensions_, "{k}", extensions.values["{field}"])' + # code += '\n\treturn element' + # compiled_code = compile(code, "<_add_extensions>", "exec") + # func = FunctionType(compiled_code.co_consts[0], globals(), "_add_extensions") + # return func def add_link_creator(self, link_fields): code = ('def _add_link(writer, element, link):' @@ -102,7 +102,8 @@ def add_metadata_creator(self, metadata_fields: List[str]): if "bounds" in metadata_fields: code += '\n\t\tmetadata_ = writer.add_bounds(metadata_, metadata.bounds)' if "extensions" in metadata_fields: - code += '\n\t\tmetadata_ = writer.add_metadata_extensions(metadata_, metadata.extensions)' + # code += '\n\t\tmetadata_ = writer.add_metadata_extensions(metadata_, metadata.extensions)' + code += '\n\t\tmetadata_ = writer.add_extensions(metadata_, metadata.extensions, writer.extensions_fields.get("metadata"))' code += '\n\treturn element' compiled_code = compile(code, "<_add_metadata>", "exec") func = FunctionType(compiled_code.co_consts[0], globals(), "_add_metadata") @@ -171,7 +172,8 @@ def add_route_creator(self, route_fields): if "type" in route_fields: code += '\n\t\troute_, _ = writer.add_subelement(route_, "type", route.type)' if "extensions" in route_fields: - code += '\n\t\troute_ = writer.add_rte_extensions(route_, route.extensions)' + # code += '\n\t\troute_ = writer.add_rte_extensions(route_, route.extensions)' + code += '\n\t\troute_ = writer.add_extensions(route_, route.extensions, writer.extensions_fields.get("rte"))' if "rtept" in route_fields: code += ('\n\t\tfor way_point in route.rtept:' '\n\t\t\troute_ = writer.add_way_point(route_, way_point)') @@ -185,10 +187,11 @@ def add_track_segment_creator(self, track_segment_fields): '\n\tif track_segment is not None:' '\n\t\ttrack_segment_ = ET.SubElement(element, track_segment.tag)') if "extensions" in track_segment_fields: - code += '\n\t\ttrack_segment_ = writer.add_trkseg_extensions(track_segment_, track_segment.extensions)' + # code += '\n\t\ttrack_segment_ = writer.add_trkseg_extensions(track_segment_, track_segment.extensions)' + code += '\n\t\ttrack_segment_ = writer.add_extensions(track_segment_, track_segment.extensions, writer.extensions_fields.get("trkseg"))' if "trkpt" in track_segment_fields: code += ('\n\t\tfor track_point in track_segment.trkpt:' - '\n\t\t\ttrack_segment_ = writer.add_way_point(track_segment_, track_point)') + '\n\t\t\ttrack_segment_ = writer.add_track_point(track_segment_, track_point)') code += '\n\treturn element' compiled_code = compile(code, "<_add_track_segment>", "exec") func = FunctionType(compiled_code.co_consts[0], globals(), "_add_track_segment") @@ -213,7 +216,8 @@ def add_track_creator(self, track_fields): if "type" in track_fields: code += '\n\t\ttrack_, _ = writer.add_subelement(track_, "type", track.type)' if "extensions" in track_fields: - code += '\n\t\ttrack_ = writer.add_trk_extensions(track_, track.extensions)' + # code += '\n\t\ttrack_ = writer.add_trk_extensions(track_, track.extensions)' + code += '\n\t\ttrack_ = writer.add_extensions(track_, track.extensions, writer.extensions_fields.get("trk"))' if "trkseg" in track_fields: code += ('\n\t\tfor track_seg in track.trkseg:' '\n\t\t\ttrack_ = writer.add_track_segment(track_, track_seg)') @@ -267,8 +271,61 @@ def add_way_point_creator(self, way_point_fields): if "dgpsid" in way_point_fields: code += '\n\t\tway_point_, _ = writer.add_subelement_number(way_point_, "dgpsid", way_point.dgpsid, 0)' if "extensions" in way_point_fields: - code += '\n\t\ttrack_ = writer.add_wpt_extensions(way_point_, way_point.extensions)' + # code += '\n\t\tway_point_ = writer.add_wpt_extensions(way_point_, way_point.extensions)' + code += '\n\t\tway_point_ = writer.add_extensions(way_point_, way_point.extensions, writer.extensions_fields.get("wpt"))' code += '\n\treturn element' compiled_code = compile(code, "<_add_way_point>", "exec") func = FunctionType(compiled_code.co_consts[0], globals(), "_add_way_point") + return func + + def add_track_point_creator(self, way_point_fields): + code = ('def _add_track_point(writer, element, way_point):' + '\n\tif way_point is not None:' + '\n\t\tway_point_ = ET.SubElement(element, way_point.tag)') + if "lat" in way_point_fields: + code += '\n\t\twriter.setIfNotNone(way_point_, "lat", "{:.{}f}".format(way_point.lat, writer.precisions["lat_lon"]))' + if "lon" in way_point_fields: + code += '\n\t\twriter.setIfNotNone(way_point_, "lon", "{:.{}f}".format(way_point.lon, writer.precisions["lat_lon"]))' + if "ele" in way_point_fields: + code += '\n\t\tway_point_, _ = writer.add_subelement_number(way_point_, "ele", way_point.ele, writer.precisions["elevation"])' + if "time" in way_point_fields: + code += '\n\t\tway_point_, _ = writer.add_subelement_time(way_point_, "time", way_point.time, writer.time_format)' + if "magvar" in way_point_fields: + code += '\n\t\tway_point_, _ = writer.add_subelement_number(way_point_, "magvar", way_point.mag_var, writer.precisions["default"])' + if "geoidheight" in way_point_fields: + code += '\n\t\tway_point_, _ = writer.add_subelement_number(way_point_, "geoidheight", way_point.geo_id_height, writer.precisions["default"])' + if "name" in way_point_fields: + code += '\n\t\tway_point_, _ = writer.add_subelement(way_point_, "name", way_point.name)' + if "cmt" in way_point_fields: + code += '\n\t\tway_point_, _ = writer.add_subelement(way_point_, "cmt", way_point.cmt)' + if "desc" in way_point_fields: + code += '\n\t\tway_point_, _ = writer.add_subelement(way_point_, "desc", way_point.desc)' + if "src" in way_point_fields: + code += '\n\t\tway_point_, _ = writer.add_subelement(way_point_, "src", way_point.src)' + if "link" in way_point_fields: + code += '\n\t\tway_point_ = writer.add_link(way_point_, way_point.link)' + if "sym" in way_point_fields: + code += '\n\t\tway_point_, _ = writer.add_subelement(way_point_, "sym", way_point.sym)' + if "type" in way_point_fields: + code += '\n\t\tway_point_, _ = writer.add_subelement(way_point_, "type", way_point.type)' + if "fix" in way_point_fields: + code += '\n\t\tway_point_, _ = writer.add_subelement(way_point_, "fix", way_point.fix)' + if "sat" in way_point_fields: + code += '\n\t\tway_point_, _ = writer.add_subelement_number(way_point_, "sat", way_point.sat, 0)' + if "hdop" in way_point_fields: + code += '\n\t\tway_point_, _ = writer.add_subelement_number(way_point_, "hdop", way_point.hdop, writer.precisions["default"])' + if "vdop" in way_point_fields: + code += '\n\t\tway_point_, _ = writer.add_subelement_number(way_point_, "vdop", way_point.vdop, writer.precisions["default"])' + if "pdop" in way_point_fields: + code += '\n\t\tway_point_, _ = writer.add_subelement_number(way_point_, "pdop", way_point.pdop, writer.precisions["default"])' + if "ageofgpsdata" in way_point_fields: + code += '\n\t\tway_point_, _ = writer.add_subelement_number(way_point_, "ageofgpsdata", way_point.age_of_gps_data, writer.precisions["default"])' + if "dgpsid" in way_point_fields: + code += '\n\t\tway_point_, _ = writer.add_subelement_number(way_point_, "dgpsid", way_point.dgpsid, 0)' + if "extensions" in way_point_fields: + # code += '\n\t\tway_point_ = writer.add_trkpt_extensions(way_point_, way_point.extensions)' + code += '\n\t\tway_point_ = writer.add_extensions(way_point_, way_point.extensions, writer.extensions_fields.get("trkpt"))' + code += '\n\treturn element' + compiled_code = compile(code, "<_add_track_point>", "exec") + func = FunctionType(compiled_code.co_consts[0], globals(), "_add_track_point") return func \ No newline at end of file