Skip to content

Commit

Permalink
#9 Work in progress:
Browse files Browse the repository at this point in the history
-GPX to KML conversion

[ci skip]
  • Loading branch information
FABallemand committed Sep 3, 2023
1 parent 09ddd8f commit 38b5d9a
Show file tree
Hide file tree
Showing 10 changed files with 979 additions and 65 deletions.
103 changes: 59 additions & 44 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, Tuple, NewType
from typing import Optional, Union, List, Tuple, Dict, NewType
import logging
import webbrowser
from datetime import datetime
Expand All @@ -20,7 +20,8 @@

from ..gpx_elements import Gpx, WayPoint
from ..gpx_parser import Parser
from ..gpx_writer import Writer
from ..gpx_writer import GPXWriter
from ..kml_writer import KMLWriter
from ..utils import EARTH_RADIUS

GPX = NewType("GPX", object) # GPX forward declaration for type hint
Expand All @@ -47,14 +48,16 @@ def __init__(self, file_path: Optional[str] = None, check_schemas: bool = True,
self.file_path: str = file_path
self.parser: Parser = Parser(file_path, check_schemas, extensions_schemas)
self.gpx: Gpx = self.parser.gpx
self.writer: Writer = Writer(
self.gpx_writer: GPXWriter = GPXWriter(
self.gpx, precisions=self.parser.precisions, time_format=self.parser.time_format)
self.kml_writer: KMLWriter = KMLWriter(
self.gpx, precisions=self.parser.precisions, time_format=self.parser.time_format)
else:
logging.warning("File path does not exist")
pass

def __str__(self) -> str:
return f"file_path = {self.file_path}\nparser = {self.parser}\ngpx = {self.gpx}\nwriter = {self.writer}"
return f"file_path = {self.file_path}\nparser = {self.parser}\ngpx = {self.gpx}\nwriter = {self.gpx_writer}"

def check_schemas(self, extensions_schemas: bool = False) -> bool:
"""
Expand Down Expand Up @@ -408,19 +411,19 @@ def remove_metadata(self):
"""
Remove metadata (ie: metadata will not be written when saving the GPX object as a .gpx file).
"""
self.writer.metadata = False
self.gpx_writer.metadata = False

def remove_elevation(self):
"""
Remove elevation data (ie: elevation data will not be written when saving the GPX object as a .gpx file).
"""
self.writer.ele = False
self.gpx_writer.ele = False

def remove_time(self):
"""
Remove time data (ie: time data will not be written when saving the GPX object as a .gpx file).
"""
self.writer.time = False
self.gpx_writer.time = False

def remove_gps_errors(self):
"""
Expand Down Expand Up @@ -500,7 +503,7 @@ def to_string(self) -> str:
str
String representingth GPX object.
"""
return self.writer.gpx_to_string(self.gpx)
return self.gpx_writer.gpx_to_string(self.gpx)

def to_dataframe(
self,
Expand Down Expand Up @@ -535,6 +538,36 @@ def to_dataframe(
"""
return self.gpx.to_dataframe(projection, elevation, speed, pace, ascent_rate, ascent_speed)

def to_csv(
self,
path: str = None,
sep: str = ",",
columns: List[str] = None,
header: bool = True,
index: bool = False) -> Union[str, None]:
"""
Write the GPX object track coordinates to a .csv file.
Parameters
----------
path : str, optional
Path to the .csv file, by default None
sep : str, optional
Separator, by default ","
columns : List[str], optional
List of columns to write, by default None
header : bool, optional
Toggle header, by default True
index : bool, optional
Toggle index, by default False
Returns
-------
str
CSV like string if path is set to None.
"""
return self.gpx.to_csv(columns, path, sep, header, index)

def to_gpx(self, path: str, check_schemas: bool = True, extensions_schemas: bool = False) -> bool:
"""
Write the GPX object to a .gpx file.
Expand All @@ -553,47 +586,29 @@ def to_gpx(self, path: str, check_schemas: bool = True, extensions_schemas: bool
bool
Return False if written file does not follow checked schemas. Return True otherwise.
"""
return self.writer.write(path, check_schemas, extensions_schemas)

def to_csv(
self,
projection: bool = False,
elevation: bool = True,
speed: bool = False,
pace: bool = False,
ascent_rate: bool = False,
ascent_speed: bool = False,
path: str = "unnamed.csv",
sep: str = ",",
header: bool = True,
index: bool = False):
return self.gpx_writer.write(path, check_schemas, extensions_schemas)

def to_kml(self, path: str, styles: Optional[List[Tuple[str, Dict]]] = None, check_schemas: bool = True, extensions_schemas: bool = False) -> bool:
"""
Write the GPX object track coordinates to a .csv file.
Write the GPX object to a .kml file.
Parameters
----------
projection : bool, optional
Toggle projected coordinates, by default False
elevation : bool, optional
Toggle elevation, by default True
speed : bool, optional
Toggle speed, by default False
pace : bool, optional
Toggle pace, by default False
ascent_rate : bool, optional
Toggle ascent rate, by default False
ascent_speed : bool, optional
Toggle ascent speed, by default False
path : str, optional
Path to the .csv file, by default "unnamed.csv"
sep : str, optional
Separator, by default ","
header : bool, optional
Toggle header, by default True
index : bool, optional
Toggle index, by default False
path : str
Path to the .gpx file.
styles : List[Tuple[str, Dict]], optional
List of (style_id, style) tuples, by default None
check_schemas : bool, optional
Toggle schema verification after writting, by default True
extensions_schemas : bool, optional
Toggle extensions schema verificaton after writing. Requires internet connection and is not guaranted to work, by default False
Returns
-------
bool
Return False if written file does not follow checked schemas. Return True otherwise.
"""
self.to_dataframe(projection, elevation, speed, pace, ascent_rate, ascent_speed).to_csv(path, sep=sep, header=header, index=index)
return self.kml_writer.write(path, styles, check_schemas, extensions_schemas)

def _matplotlib_plot_text(
self,
Expand Down
89 changes: 77 additions & 12 deletions ezgpx/gpx_elements/gpx.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
from typing import List, Tuple
from typing import Union, List, Tuple
import logging
import xmlschema
import pandas as pd
Expand Down Expand Up @@ -609,16 +609,25 @@ def to_dataframe(
"""
Convert GPX object to Pandas Dataframe.
Args:
projection (bool, optional): Toggle projection. Defaults to False.
elevation (bool, optional): Toggle elevation. Defaults to True.
speed (bool, optional): Toggle speed. Defaults to False.
pace (bool, optional): Toggle pace. Defaults to False.
ascent_rate (bool, optional): Toggle ascent rate. Defaults to False.
ascent_speed (bool, optional): Toggle ascent speed. Defaults to False.
Returns:
pd.DataFrame: Dataframe containing data from GPX.
Parameters
----------
projection : bool, optional
Toggle projection, by default False
elevation : bool, optional
Toggle elevation, by default True
speed : bool, optional
Toggle speed, by default False
pace : bool, optional
Toggle pace, by default False
ascent_rate : bool, optional
Toggle ascent rate, by default False
ascent_speed : bool, optional
Toggle ascent speed, by default False
Returns
-------
pd.DataFrame
Dataframe containing data from GPX.
"""
test_point = self.first_point()
if projection and test_point._x is None:
Expand All @@ -641,7 +650,10 @@ def to_dataframe(
"lon": track_point.lon
}
if elevation:
track_point_dict["ele"] = track_point.ele
if track_point.ele is not None:
track_point_dict["ele"] = track_point.ele
else:
track_point_dict["ele"] = 0
if projection:
track_point_dict["x"] = track_point._x
track_point_dict["y"] = track_point._y
Expand All @@ -657,6 +669,59 @@ def to_dataframe(
df = pd.DataFrame(route_info)
return df

def to_csv(
self,
path: str = None,
sep: str = ",",
columns: List[str] = None,
header: bool = True,
index: bool = False) -> Union[str, None]:
"""
Write the GPX object track coordinates to a .csv file.
Parameters
----------
path : str, optional
Path to the .csv file, by default None
sep : str, optional
Separator, by default ","
columns : List[str], optional
List of columns to write, by default None
header : bool, optional
Toggle header, by default True
index : bool, optional
Toggle index, by default False
Returns
-------
str
CSV like string if path is set to None.
"""
if columns is None:
columns = ["lat", "lon"]

elevation = False
projection = False
speed = False
pace = False
ascent_rate = False
ascent_speed = False

if "ele" in columns:
elevation = True
if "x" in columns or "y" in columns:
projection = True
if "speed" in columns:
speed = True
if "pace" in columns:
pace = True
if "ascent_rate" in columns:
ascent_rate = True
if "ascent_speed" in columns:
ascent_speed = True

return self.to_dataframe(projection, elevation, speed, pace, ascent_rate, ascent_speed).to_csv(path, sep=sep, columns=columns, header=header, index=index)

def project(self, projection: str):
"""
Project tracks.
Expand Down
2 changes: 1 addition & 1 deletion ezgpx/gpx_writer/__init__.py
Original file line number Diff line number Diff line change
@@ -1 +1 @@
from .writer import *
from .gpx_writer import *
11 changes: 5 additions & 6 deletions ezgpx/gpx_writer/writer.py → ezgpx/gpx_writer/gpx_writer.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
from ..gpx_elements import Bounds, Copyright, Email, Extensions, Gpx, Link, Metadata, Person, PointSegment, Point, Route, TrackSegment, Track, WayPoint
from ..gpx_parser import Parser, DEFAULT_PRECISION, DEFAULT_TIME_FORMAT

class Writer():
class GPXWriter():
"""
GPX file writer.
"""
Expand All @@ -26,7 +26,7 @@ def __init__(
precisions: dict = None,
time_format: str = None) -> None:
"""
Initialize Writer instance.
Initialize GPXWriter instance.
Args:
gpx (Gpx, optional): Gpx instance to write. Defaults to None.
Expand Down Expand Up @@ -241,7 +241,7 @@ def add_metadata(self, element: ET.Element, metadata: Metadata) -> ET.Element:
xml.etree.ElementTree.Element: GPX element.
"""
if metadata is not None:
metadata_ = ET.SubElement(self.gpx_root, metadata.tag)
metadata_ = ET.SubElement(element, metadata.tag)
metadata_, _ = self.add_subelement(metadata_, "name", metadata.name)
metadata_, _ = self.add_subelement(metadata_, "desc", metadata.desc)
metadata_ = self.add_person(metadata_, metadata.author)
Expand Down Expand Up @@ -492,7 +492,7 @@ def add_root_extensions(self) -> None:

def gpx_to_string(self) -> str:
"""
Convert Gpx instance to string.
Convert Gpx instance to a string (the content of a .gpx file).
Returns:
str: String corresponding to the Gpx instance.
Expand Down Expand Up @@ -578,5 +578,4 @@ def write(self, path: str, check_schemas: bool = False, extensions_schemas: bool
if not verification_gpx.check_schemas(self.path, extensions_schemas=extensions_schemas):
logging.error("Invalid written file (does not follow schema)")
return False
return True

return True
1 change: 1 addition & 0 deletions ezgpx/kml_writer/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
from .kml_writer import *
Loading

0 comments on commit 38b5d9a

Please sign in to comment.