source: OpenWorkouts-current/ow/utilities.py @ 02aee97

currentfeature/docs
Last change on this file since 02aee97 was 02aee97, checked in by Borja Lopez <borja@…>, 5 years ago

(#52) Ensure the generated map screenshots have the proper permissions

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