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
RevLine 
[5ec3a0b]1import re
[d1c4782]2import os
3import logging
[9ab0fe3]4import calendar
[d6f8304]5import shutil
6import time
[2f8a48f]7from datetime import datetime, timedelta
[53bb3e5]8from decimal import Decimal
9from shutil import copyfileobj
10
[5ec3a0b]11from unidecode import unidecode
12from xml.dom import minidom
[53bb3e5]13from ZODB.blob import Blob
[d6f8304]14from splinter import Browser
15
[5ec3a0b]16
[d1c4782]17log = logging.getLogger(__name__)
18
[5ec3a0b]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:
[53bb3e5]93                    t = datetime.strptime(
[5ec3a0b]94                        rfc3339, '%Y-%m-%dT%H:%M:%S.%fZ')
95                except ValueError:
[53bb3e5]96                    t = datetime.strptime(
[5ec3a0b]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})
[53bb3e5]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
[119412d]183def create_blob(data, file_extension, binary=False):
[53bb3e5]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
[119412d]191        if not binary and not isinstance(data, bytes):
[53bb3e5]192            data = data.encode('utf-8')
193        open_blob.write(data)
194    return blob
[d1c4782]195
196
[d6f8304]197def save_map_screenshot(workout, request):
198
[d1c4782]199    if workout.has_gpx:
200
[d6f8304]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__))
[d1c4782]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
[d6f8304]226        shutil.move(splinter_screenshot_path, screenshot_path)
[02aee97]227        os.chmod(screenshot_path, 0o644)
[d1c4782]228        return True
229
230    return False
[2f8a48f]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
[9ab0fe3]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.