gpxity package

gpxity.track module

This module defines Track.

class gpxity.track.Track(gpx=None)[source]

Bases: object

Represents a track.

An track is essentially a GPX file. If a backend supports attributes not directly supported by the GPX format like the MapMyTracks track type, they will transparently be encodeded in existing GPX fields like keywords, see keywords.

The GPX part is done by https://github.com/tkrajina/gpxpy.

If a track is assigned to a backend, all changes will by default be written directly to the backend. Some backends are able to change only one attribute with little time overhead, others always have to rewrite the entire track.

However you can use the context manager batch_changes(). This holds back updating the backend until leaving the context.

If you manipulate the gpx directly, this goes unnoticed. Use rewrite() when done.

Not all backends support everything, you could get the exception NotImplementedError.

Some backends are able to change only one attribute with little time overhead, others always have to rewrite the entire track.

All points are always rounded to 6 decimal digits when they are added to the track.

Parameters:gpx (GPX) – Initial content. To be used if you create a new Track from scratch without loading it from some backend.

The data will only be loaded from the backend when it is needed. Some backends might support loading some attributes separately, but for now, we always load everything as soon as anything is needed.

legal_categories

tuple(str) – The legal values for category. The first one is used as default value. This is a mostly a superset of the values for the different backends. Every backend maps from its internal values into those when reading and maps them back when writing. Since not all backends support all values defined here and since some backends may define more values than we know, information may get lost when converting, even if you copy a track between two backends of the same type.

exception CannotMerge[source]

Bases: Exception

Is raised if Track.merge() fails.

add_points(points) → None[source]

Add points to last segment in the last track.

If no track is allocated yet and points is not an empty list, allocates a track.

Parameters:points (list(GPXTrackPoint) – The points to be added
adjust_time(delta)[source]

Add a timedelta to all times.

gpxpy.gpx.adjust_time does the same but it ignores waypoints. A newer gpxpy.py has a new bool arg for adjust_time which also adjusts waypoints on request but I do not want to check versions.

angle() → float[source]

For me, the earth is flat.

Returns:the angle in degrees 0..360 between start and end. If we have no track, return 0
backend

The backend this track lives in. If the track was constructed in memory, backend is None.

This is a read-only property. It is set with Backend.add.

It is not possible to decouple a track from its backend, use clone().

Returns:The backend
batch_changes()[source]

Context manager: disable the direct update in the backend and saves the entire track when done.

This may or may not make things faster. Directory and GPSIES profits from this, MMT maybe.

can_merge(other, partial_tracks: bool = False)[source]

Check if self and other are mergeable.

Parameters:
  • other – The other Track
  • partial_tracks – If True, they are mergeable if one of them contains the other one.
Returns: (int, str)
int is either None or the starting index of the shorter track in the longer track str is either None or a string explaing why this is not mergeable
category

str – What is this track doing? If we have no current value, return the default.

The value is automatically translated between our internal value and the value used by the backend. This happens when reading from or writing to the backend. :returns: The current value or the default value (see legal_categories)

change_keywords(values, replace=False, dry_run=False)[source]

Change keywords.

Duplicate keywords are silently ignored. A keyword may not contain a comma. Keywords with a preceding ‘-‘ are removed, the others are added. Raise an Exception if a keyword is both added and removed.

Parameters:
  • values – Either a single str with one or more keywords, separated by commas or an iterable of keywords
  • replace – if True, replace current keywords with the new ones. Ignores keywords preceded with a ‘-‘.
  • dry_run – if True, only return the new keywords but do not make that change
Returns:

The new keywords

clone()[source]

Create a new track with the same content but without backend.

Returns:the new track
Return type:Track
description

str – The description.

Returns:The description
distance() → float[source]

For me, the earth is flat.

Returns:the distance in km, rounded to m
fix(orux: bool = False, jumps: bool = False)[source]

Fix bugs. This may fix them or produce more bugs.

Please backup your track before doing this.

Parameters:
  • orux – Older Oruxmaps switched the day back by one day after exactly 24 hours.
  • jumps – Whenever the time jumps back or more than 30
  • into the future, split the segment at that point. (minutes) –
Returns:

A list of message strings, usable for verbose output.

gpx

Direct access to the GPX object.

If you use it to change its content, remember to call rewrite() afterwards.

Returns:the GPX object
id_in_backend

Every backend has its own scheme for unique track ids.

Some backends may change the id if the track data changes.

Returns:the id in the backend
ids

Return ids for all backends where this track has already been.

This is a list of pairs. pair[0] is the name of the backend, pair[1] is the track id within. You can modify it but your changes will never be saved. They are sorted by ascending age. Only the 5 youngest are kept. If the same filename appears in more than one directory, keep only the youngest.

Returns: list( (str, str))
a list of tuple pairs with str(backend) and id_in_backend
index(other, digits=4)[source]

Check if this track contains other track.gpx.

This only works if all values for latitude and longitude are nearly identical.

Useful if one of the tracks had geofencing applied.

Parameters:digits – How many after point digits are used
Returns:None or the starting index for other.points in self.points
key(with_category: bool = True, with_last_time: bool = True) → str[source]

For speed optimized equality checks, not granted to be exact, but sufficiently safe IMHO.

Parameters:
  • with_category – If False, do not use self.category. Needed for comparing tracks for equality like in unittests because values can change and information can get lost while copying between different backends
  • with_last_time – If False, do not use self.last_time.
Returns:

a string with selected attributes in printable form.

keywords

list(str) – represent them as a sorted list - in GPX they are comma separated.

Content is whatever you want.

Because the GPX format does not have attributes for everything used by all backends, we encode some of the backend arguments in keywords.

Example for mapmytracks: keywords = ‘Status:public, Category:Cycling’.

However this is transparent for you. When parsing theGPX file, those are removed from keywords, and the are re-added in when exporting in to_xml(). So Track.keywords will never show those special values.

Some backends may change keywords. MMT converts the first character into upper case and will return it like that. Gpxity will not try to hide such problems. So if you save a track in MMT, its keywords will change. But they will not change if you copy from MMT to Directory - so if you copy from DirectoryA to MMT to DirectoryB, the keywords in DirectoryA and DirectoryB will not be identical, for example “berlin” in DirectoryA but “Berlin” in DirectoryB.

last_point()[source]

Return the last point of the track.

last_time

The last time we received.

Returns:The last time we received so far. If none, return None.
merge(other, remove: bool = False, dry_run: bool = False, copy: bool = False, partial_tracks: bool = False) → list[source]

Merge other track into this one. The track points must be identical.

If merging is not possible, raise Track.CannotMerge.

If either is public, the result is public. If self.title seems like a default and other.title does not, use other.title Combine description and keywords.

Parameters:
  • other (Track) – The track to be merged
  • remove – After merging succeeded, remove other
  • dry_run – if True, do not really apply the merge
  • copy – This argument is ignored. It is only here to give Track.merge() and Backend.merge() the same interface.
  • partial_tracks – merges other track if either track is part of the other one
Returns: list(str)
Messages about what has been done.
moving_speed() → float[source]

Speed for time in motion in km/h.

Returns:The moving speed
static overlapping_times(tracks)[source]

Find tracks with overlapping times.

Yields:groups of tracks with overlapping times. Sorted by time.

This may be very slow for many long tracks.

parse(indata)[source]

Parse GPX.

title, description and category from indata have precedence over the current values. public will be or-ed keywords will stay unchanged if indata has none, otherwise be replaced from indata

Parameters:indata – may be a file descriptor or str
point_list()[source]

A flat list with all points.

Returns:The list
points()[source]

A generator over all points.

Yields:GPXTrackPoint – all points in all tracks and segments
points_equal(other, digits=4) → bool[source]

Compare points for same position.

Parameters:digits – Number of after comma digits to compare
Returns:True if both tracks have identical points.

All points of all tracks and segments are combined. Elevations are ignored.

points_hash() → float[source]

A hash that is hopefully different for every possible track.

It is built using the combination of all points.

Returns:The hash
public

bool

Is this a private track (can only be seen by the account holder) or is it public?.

Default value is False
Returns:True if track is public, False if it is private
remove()[source]

Remove this track in the associated backend.

If the track is not coupled with a backend, raise an Exception.

rewrite() → None[source]

Call this after you directly manipulated gpx.

segments()[source]

A generator over all segments.

Yields:GPXTrackSegment – all segments in all tracks
similarity(other)[source]

Return a float 0..1: 1 is identity.

speed() → float[source]

Speed over the entire time in km/h or 0.0.

Returns:The speed
split()[source]

Create separate tracks for every track/segment.

time

datetime.datetime – start time of track.

For a simpler implementation of backends, notably MMT, we ignore gpx.time. Instead we return the time of the earliest track point. Only if there is no track point, return gpx.time. If that is unknown too, return None.

For the same reason time is readonly.

We assume that the first point comes first in time and the last point comes last in time. In other words, points should be ordered by their time.

time_offset(other)[source]

If time and last_time have the same offset between both tracks, return that time difference. Otherwise return None.

title

str – The title.

Returns:the title
to_xml() → str[source]

Produce exactly one line per trackpoint for easier editing (like removal of unwanted points).

Returns:The xml string.
warnings()[source]

Return a list of strings with easy to find problems.

gpxity.auth module

This module defines Authenticate.

class gpxity.auth.Authenticate(backend, url, username: str = None)[source]

Bases: object

Get password and / or Url from auth.cfg.

If nothing is useable, sets them to None.

auth.cfg is expected in ~/.config/Gpxity/auth.cfg

Danger

auth.cfg is not encrypted. Better not use this unless you know what you are doing!

Parameters:
  • backend (Backend) – The backend
  • username (str) – For the wanted account in the backend
path

str – The name for the auth file. Class variable, to be changed before Authenticate() is instantiated.

auth

tuple(str,str) – (username, password). Both are either str or None.

url

If given, overrides the url given to the backend

For every specific account in a backend, auth.cfg has a section. Its name is case sensitive: The ClassName must match exactly.

  • [ClassName.username]
A section can define
  • Password
  • Url
  • Mysql, used by WPTrackserver

An example for user gpxitytest on gpsies.com:

[GPSIES:gpxitytest]
Password = the_unencrypted_password

A mail account:

[Mailer:gpxitytest]
Url = tester@test.test
path = '~/.config/Gpxity/auth.cfg'

gpxity.backend module

This module defines Backend.

class gpxity.backend.Backend(url: str = None, auth=None, cleanup: bool = False, timeout=None)[source]

Bases: object

A place where tracks live. Something like the filesystem or http://mapmytracks.com.

A Backend should hold only tracks for one person, and they should not overlap in time. This is not enforced but sometimes behaviour is undefined if you ignore this.

A Backend be used as a context manager. Upon termination, all tracks may be removed automatically by setting cleanup=True. Some concrete implementations may also remove the backend itself.

A Backend allows indexing by normal int index, by Track and by Track.id_in_backend. if 'ident' in backend is possible. len(backend) shows the number of tracks. Please note that Code like if backend: may not behave as expected. This will be False if the backend has no track. If that is not what you want, consider if backend is not None

The backend will automatically synchronize. So something like len(Backend()) will work. However, some other Backend pointing to the same storage or even a different process might change things. If you want to cope with that, use scan().

Not all backends support all methods. The unsupported methods will raise NotImplementedError. As a convenience every backend has a list supported to be used like if 'track' in backend.supported: where track is the name of the method.

Backends support no locking. If others modify a backend concurrently, you may get surprises. It is up to you to handle those.

Some backends may use cookies.

Parameters:
  • url (str) – Initial value for url
  • auth (str) – The username. This will lookup the password and config from Authenticate. You can also pass a dict containing what would normally be obtained from Authenticate. The dict must also contain ‘Username’.
  • cleanup (bool) – If true, destroy() will remove all tracks.
supported

set(str) – The names of supported methods. Creating the first instance of the backend initializes this. Only methods which may not be supported are mentioned here. If a particular value write_* like write_public does not exist, the entire track is written instead which normally results in a new ident for the track.

full_support

set(str) – All possible values for the supported attribute.

url

str – the address. May be a real URL or a directory, depending on the backend implementation. Every implementation may define its own default for url. Must never end with ‘/’ except for Directory(url=’/’).

timeout

If None, there are no timeouts: Gpxity waits forever. For legal values see http://docs.python-requests.org/en/master/user/advanced/#timeouts

config

A Section with all entries in auth.cfg for this backend

needs_config

If True, the Backend class expects data in auth.cfg

exception BackendException[source]

Bases: Exception

Is raised for general backend exceptions, especially error messages from a remote server

exception NoMatch[source]

Bases: Exception

Is raised if a track is expected to pass the match filter but does not

add(track)[source]

Add a track to this backend.

We do not check if it already exists in this backend. No track already existing in this backend will be overwritten, the id_in_backend of track will be deduplicated if needed. This is currently only needed for Directory. Note that some backends reject a track if it is very similar to an existing track even if it belongs to some other user.

If the track object is already in the list of tracks, raise ValueError.

If the track does not pass the current match function, raise an exception.

Parameters:track (Track) – The track we want to save in this backend.
Returns:The saved track. If the original track lives in a different backend, a new track living in this backend will be created and returned.
Return type:Track
classmethod all_backend_classes(exclude=None, needs=None)[source]

Find all backend classes.

Parameters:
  • exclude – A list with classes to be excluded
  • needs – set(str) with needed supported actions
Returns:

A list of all backend classes. Disabled backends are not returned.

clone()[source]

return a clone.

decode_category(value: str) → str[source]

Translate the value from the backend into internal one.

default_url = None
destroy()[source]

If cleanup was set at init time, removes all tracks.

Some backends (example: Directory)

may also remove the account (or directory). See also remove_all().

encode_category(value: str) → str[source]

Translate internal value into the backend specific value.

classmethod find_class(name: str)[source]

Find the Backend class name “name”.

Parameters:name – May be anycase (upper,lower)
Returns:the backend class or None
flush()[source]

Some backends delay actual writing. This enforces writing.

Currently, only the Mailer backend can delay, it will bundle all mailed tracks into one mail instead of sending separate mails for every track. Needed for lifetracking.

full_support = ('scan', 'remove', 'lifetrack', 'lifetrack_end', 'get_time', 'write', 'write_title', 'write_public', 'write_category', 'write_description', 'keywords', 'write_add_keywords', 'write_remove_keywords')
get_time() → datetime.datetime[source]

get time from the server where backend is located as a Linux timestamp. A backend implementation does not have to support this.

classmethod instantiate(name, timeout=None)[source]

Instantiate a Backend out of its identifier.

Parameters:timeout – Needed for creating backends like MMT or GPSIES
Returns:A backend. If it has already been instantiated, return the cached value.
classmethod is_disabled() → bool[source]

True if this backend is disabled by env variable GPXITY_DISABLE_BACKENDS.

This variable is a comma separated list of Backend class names.

Returns:True if disabled
legal_categories

A list with all legal categories.

Returns: list(str)
all legal values for this backend
match

Filter tracks.

A function with one argument returning None or str. The backend will call

this with every track and ignore tracks where match does not return None. The returned str should explain why the track does not match.

If you change a track such that it does not match anymore, the exception NoMatch will be raised and the match stays unchanged.

matches(track, exc_prefix: str = None)[source]

match track against the current match function.

Parameters:exc_prefix – If not None, use it for the beginning of an exception message. If None, never raise an exception
Returns:True for match
merge(other, remove: bool = False, dry_run: bool = False, copy: bool = False, partial_tracks: bool = False) → list[source]

merge other backend or a single track into this one. Tracks within self are also merged.

If two tracks have identical points, or-ify their other attributes.

Parameters:
  • other – The backend or a single track to be merged
  • remove – If True, remove merged tracks
  • dry_run – If True, do not really merge or remove
  • copy – Do not try to find a matching track, just copy other into this Backend
  • partial_tracks – If True, two tracks are mergeable if one of them contains the other one.
Returns:

list(str) A list of messages for verbose output

needs_config = True
classmethod parse_objectname(name)[source]

Parse the full identifier for a track.

Parameters:name – the full identifier for a Track
Returns:A tuple with class, account,track_id
real_len() → int[source]

len(backend) without calling scan() first.

Returns:the length
remove(value) → None[source]

Remove track. This can also be done for tracks not passing the current match function.

Parameters:value – If it is not an Track, remove() looks it up by doing self[value]
remove_all()[source]

Remove all tracks we know about.

If their id_in_backend has meanwhile been changed through another backend instance or another process, we cannot find it anymore. We do not rescan all tracks in the backend. If you want to make sure it will be empty, call scan() first.

If you use a match function, only matching tracks will be removed.

scan(now: bool = False) → None[source]

Enforce a reload of the list of all tracks in the backend.

This will be delayed until the list is actually needed again.

If this finds an unsaved track not matching the current match function, an exception is thrown. Saved Tracks not matching the current match will no be loaded.

Parameters:now – If True, do not delay scanning.
supported = set()
url

get self.config[‘url’].

Returns: The url