source: OpenWorkouts-current/ow/utilities.py @ 2a71053

currentfeature/docs
Last change on this file since 2a71053 was 9ab0fe3, checked in by borja <borja@…>, 5 years ago

Added method to return the number of week a given day is (1, 2, 3, etc),

relative to the month the day is in.

  • Property mode set to 100644
File size: 7.3 KB
Line 
1import re
2import os
3import logging
4import calendar
5import subprocess
6from datetime import datetime, timedelta
7from decimal import Decimal
8from shutil import copyfileobj
9
10from unidecode import unidecode
11from xml.dom import minidom
12from ZODB.blob import Blob
13
14log = logging.getLogger(__name__)
15
16
17def slugify(text, delim=u'-'):
18    """
19    Generates an ASCII-only slug.
20    from http://flask.pocoo.org/snippets/5/
21    """
22    _punct_re = re.compile(r'[\t !"#$%&\'()*\-/<=>?@\[\\\]^_`{|},.]+')
23    result = []
24    text = unidecode(text)
25    for word in _punct_re.split(text.lower()):
26        result.extend(word.split())
27    return delim.join(result)
28
29
30class GPXMinidomParser(object):
31    """
32    GPX parser, using minidom from the base library.
33
34    We need this as a workaround, as gpxpy does not handle GPX 1.1 extensions
35    correctly right now (and we have not been able to fix it).
36
37    This method is inspired by this blog post:
38
39    http://castfortwo.blogspot.com.au/2014/06/
40    parsing-strava-gpx-file-with-python.html
41    """
42
43    def __init__(self, gpx_path):
44        self.gpx_path = gpx_path
45        self.gpx = None
46        self.tracks = {}
47
48    def load_gpx(self):
49        """
50        Load the given gpx file into a minidom doc, normalize it and set
51        self.gpx to the document root so we can reuse it later on
52        """
53        doc = minidom.parse(self.gpx_path)
54        doc.normalize()
55        self.gpx = doc.documentElement
56
57    def parse_tracks(self):
58        """
59        Loop over all the tracks found in the gpx, parsing them
60        """
61        for trk in self.gpx.getElementsByTagName('trk'):
62            self.parse_track(trk)
63
64    def parse_track(self, trk):
65        """
66        Parse the given track, extracting all the information and putting it
67        into a dict where the key is the track name and the value is a list
68        of data for the the different segments and points in the track.
69
70        All the data is saved in self.tracks
71        """
72        name = trk.getElementsByTagName('name')[0].firstChild.data
73        if name not in self.tracks:
74            self.tracks[name] = []
75
76        for trkseg in trk.getElementsByTagName('trkseg'):
77            for trkpt in trkseg.getElementsByTagName('trkpt'):
78                lat = Decimal(trkpt.getAttribute('lat'))
79                lon = Decimal(trkpt.getAttribute('lon'))
80
81                # There could happen there is no elevation data
82                ele = trkpt.getElementsByTagName('ele')
83                if ele:
84                    ele = Decimal(ele[0].firstChild.data)
85                else:
86                    ele = None
87
88                rfc3339 = trkpt.getElementsByTagName('time')[0].firstChild.data
89                try:
90                    t = datetime.strptime(
91                        rfc3339, '%Y-%m-%dT%H:%M:%S.%fZ')
92                except ValueError:
93                    t = datetime.strptime(
94                        rfc3339, '%Y-%m-%dT%H:%M:%SZ')
95
96                hr = None
97                cad = None
98                atemp = None
99                extensions = trkpt.getElementsByTagName('extensions')
100                if extensions:
101                    extensions = extensions[0]
102                    trkPtExt = extensions.getElementsByTagName(
103                        'gpxtpx:TrackPointExtension')[0]
104                    if trkPtExt:
105                        hr_ext = trkPtExt.getElementsByTagName('gpxtpx:hr')
106                        cad_ext = trkPtExt.getElementsByTagName('gpxtpx:cad')
107                        atemp_ext = trkPtExt.getElementsByTagName(
108                            'gpxtpx:atemp')
109                        if hr_ext:
110                            hr = Decimal(hr_ext[0].firstChild.data)
111                        if cad_ext:
112                            cad = Decimal(cad_ext[0].firstChild.data)
113                        if atemp_ext:
114                            atemp = Decimal(atemp_ext[0].firstChild.data)
115
116                self.tracks[name].append({
117                    'lat': lat,
118                    'lon': lon,
119                    'ele': ele,
120                    'time': t,
121                    'hr': hr,
122                    'cad': cad,
123                    'atemp': atemp})
124
125
126def semicircles_to_degrees(semicircles):
127    return semicircles * (180 / pow(2, 31))
128
129
130def degrees_to_semicircles(degrees):
131    return degrees * (pow(2, 31) / 180)
132
133
134def miles_to_kms(miles):
135    factor = 0.62137119
136    return miles / factor
137
138
139def kms_to_miles(kms):
140    factor = 0.62137119
141    return kms * factor
142
143
144def meters_to_kms(meters):
145    return meters / 1000
146
147
148def kms_to_meters(kms):
149    return kms * 1000
150
151
152def mps_to_kmph(mps):
153    """
154    Transform a value from meters-per-second to kilometers-per-hour
155    """
156    return mps * 3.6
157
158
159def kmph_to_mps(kmph):
160    """
161    Transform a value from kilometers-per-hour to meters-per-second
162    """
163    return kmph * 0.277778
164
165
166def copy_blob(blob):
167    """
168    Create a copy of a blob object, returning another blob object that is
169    the copy of the given blob file.
170    """
171    new_blob = Blob()
172    if getattr(blob, 'file_extension', None):
173        new_blob.file_extension = blob.file_extension
174    with blob.open('r') as orig_blob, new_blob.open('w') as dest_blob:
175        orig_blob.seek(0)
176        copyfileobj(orig_blob, dest_blob)
177    return new_blob
178
179
180def create_blob(data, file_extension, binary=False):
181    """
182    Create a ZODB blob file from some data, return the blob object
183    """
184    blob = Blob()
185    blob.file_extension = file_extension
186    with blob.open('w') as open_blob:
187        # use .encode() to convert the string to bytes if needed
188        if not binary and not isinstance(data, bytes):
189            data = data.encode('utf-8')
190        open_blob.write(data)
191    return blob
192
193
194def save_map_screenshot(workout):
195    if workout.has_gpx:
196        current_path = os.path.abspath(os.path.dirname(__file__))
197        tool_path = os.path.join(current_path, '../bin/screenshot_map')
198
199        screenshots_path = os.path.join(
200            current_path, 'static/maps', str(workout.owner.uid))
201        if not os.path.exists(screenshots_path):
202            os.makedirs(screenshots_path)
203
204        screenshot_path = os.path.join(
205            screenshots_path, str(workout.workout_id))
206        screenshot_path += '.png'
207
208        subprocess.run(
209            [tool_path, str(workout.owner.uid), str(workout.workout_id),
210             screenshot_path], stdout=subprocess.PIPE, stderr=subprocess.PIPE)
211
212        return True
213
214    return False
215
216
217def timedelta_to_hms(value):
218    """
219    Return hours, minutes, seconds from a timedelta object
220    """
221    hours, remainder = divmod(int(value.total_seconds()), 3600)
222    minutes, seconds = divmod(remainder, 60)
223    return hours, minutes, seconds
224
225
226def get_week_days(day, start_day=1):
227    """
228    Return a list of datetime objects for the days of the week "day" is in.
229
230    day is a datetime object (like in datetime.now() for "today")
231
232    start_day can be used to set if week starts on monday (1) or sunday (0)
233    """
234    first_day = day - timedelta(days=day.isoweekday() - start_day)
235    week_days = [first_day + timedelta(days=i) for i in range(7)]
236    return week_days
237
238
239def get_month_week_number(day):
240    """
241    Given a datetime object (day), return the number of week the day is
242    in the current month (week 1, 2, 3, etc)
243    """
244    weeks = calendar.monthcalendar(day.year, day.month)
245    for week in weeks:
246        if day.day in week:
247            return weeks.index(week)
248    return None
Note: See TracBrowser for help on using the repository browser.