source: OpenWorkouts-current/ow/utilities.py @ d1c4782

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

Show a capture of the workout map, as an image, in the dashboard:

  • Added a view to render the tracking map of a workout full screen
  • Added a small shell script that uses chrome to grabs a screenshot of the full screen map view of a workout, then uses imagemagick convert to crop/resize it and finally saves it in a given location
  • Added a static/maps directory to store maps captures
  • Added static/maps to the boring/ignore file
  • Added a tool in utilities.py to call the shell script that captures the screenshot of the map
  • Added a method to the Workout model, that returns the static path to the workout map capture (valid to use with request.static_url()). If there is no capture yet, call the tool to make one
  • Added code to dashboard.pt to show the capture of the map
  • Added a new parameter to te ow maps js code, allowing us to hide/show the zoom controls of the map when building a new one
  • Property mode set to 100644
File size: 6.3 KB
Line 
1import re
2import os
3import logging
4import subprocess
5from datetime import datetime
6from decimal import Decimal
7from shutil import copyfileobj
8
9from unidecode import unidecode
10from xml.dom import minidom
11from ZODB.blob import Blob
12
13log = logging.getLogger(__name__)
14
15
16def slugify(text, delim=u'-'):
17    """
18    Generates an ASCII-only slug.
19    from http://flask.pocoo.org/snippets/5/
20    """
21    _punct_re = re.compile(r'[\t !"#$%&\'()*\-/<=>?@\[\\\]^_`{|},.]+')
22    result = []
23    text = unidecode(text)
24    for word in _punct_re.split(text.lower()):
25        result.extend(word.split())
26    return delim.join(result)
27
28
29class GPXMinidomParser(object):
30    """
31    GPX parser, using minidom from the base library.
32
33    We need this as a workaround, as gpxpy does not handle GPX 1.1 extensions
34    correctly right now (and we have not been able to fix it).
35
36    This method is inspired by this blog post:
37
38    http://castfortwo.blogspot.com.au/2014/06/
39    parsing-strava-gpx-file-with-python.html
40    """
41
42    def __init__(self, gpx_path):
43        self.gpx_path = gpx_path
44        self.gpx = None
45        self.tracks = {}
46
47    def load_gpx(self):
48        """
49        Load the given gpx file into a minidom doc, normalize it and set
50        self.gpx to the document root so we can reuse it later on
51        """
52        doc = minidom.parse(self.gpx_path)
53        doc.normalize()
54        self.gpx = doc.documentElement
55
56    def parse_tracks(self):
57        """
58        Loop over all the tracks found in the gpx, parsing them
59        """
60        for trk in self.gpx.getElementsByTagName('trk'):
61            self.parse_track(trk)
62
63    def parse_track(self, trk):
64        """
65        Parse the given track, extracting all the information and putting it
66        into a dict where the key is the track name and the value is a list
67        of data for the the different segments and points in the track.
68
69        All the data is saved in self.tracks
70        """
71        name = trk.getElementsByTagName('name')[0].firstChild.data
72        if name not in self.tracks:
73            self.tracks[name] = []
74
75        for trkseg in trk.getElementsByTagName('trkseg'):
76            for trkpt in trkseg.getElementsByTagName('trkpt'):
77                lat = Decimal(trkpt.getAttribute('lat'))
78                lon = Decimal(trkpt.getAttribute('lon'))
79
80                # There could happen there is no elevation data
81                ele = trkpt.getElementsByTagName('ele')
82                if ele:
83                    ele = Decimal(ele[0].firstChild.data)
84                else:
85                    ele = None
86
87                rfc3339 = trkpt.getElementsByTagName('time')[0].firstChild.data
88                try:
89                    t = datetime.strptime(
90                        rfc3339, '%Y-%m-%dT%H:%M:%S.%fZ')
91                except ValueError:
92                    t = datetime.strptime(
93                        rfc3339, '%Y-%m-%dT%H:%M:%SZ')
94
95                hr = None
96                cad = None
97                atemp = None
98                extensions = trkpt.getElementsByTagName('extensions')
99                if extensions:
100                    extensions = extensions[0]
101                    trkPtExt = extensions.getElementsByTagName(
102                        'gpxtpx:TrackPointExtension')[0]
103                    if trkPtExt:
104                        hr_ext = trkPtExt.getElementsByTagName('gpxtpx:hr')
105                        cad_ext = trkPtExt.getElementsByTagName('gpxtpx:cad')
106                        atemp_ext = trkPtExt.getElementsByTagName(
107                            'gpxtpx:atemp')
108                        if hr_ext:
109                            hr = Decimal(hr_ext[0].firstChild.data)
110                        if cad_ext:
111                            cad = Decimal(cad_ext[0].firstChild.data)
112                        if atemp_ext:
113                            atemp = Decimal(atemp_ext[0].firstChild.data)
114
115                self.tracks[name].append({
116                    'lat': lat,
117                    'lon': lon,
118                    'ele': ele,
119                    'time': t,
120                    'hr': hr,
121                    'cad': cad,
122                    'atemp': atemp})
123
124
125def semicircles_to_degrees(semicircles):
126    return semicircles * (180 / pow(2, 31))
127
128
129def degrees_to_semicircles(degrees):
130    return degrees * (pow(2, 31) / 180)
131
132
133def miles_to_kms(miles):
134    factor = 0.62137119
135    return miles / factor
136
137
138def kms_to_miles(kms):
139    factor = 0.62137119
140    return kms * factor
141
142
143def meters_to_kms(meters):
144    return meters / 1000
145
146
147def kms_to_meters(kms):
148    return kms * 1000
149
150
151def mps_to_kmph(mps):
152    """
153    Transform a value from meters-per-second to kilometers-per-hour
154    """
155    return mps * 3.6
156
157
158def kmph_to_mps(kmph):
159    """
160    Transform a value from kilometers-per-hour to meters-per-second
161    """
162    return kmph * 0.277778
163
164
165def copy_blob(blob):
166    """
167    Create a copy of a blob object, returning another blob object that is
168    the copy of the given blob file.
169    """
170    new_blob = Blob()
171    if getattr(blob, 'file_extension', None):
172        new_blob.file_extension = blob.file_extension
173    with blob.open('r') as orig_blob, new_blob.open('w') as dest_blob:
174        orig_blob.seek(0)
175        copyfileobj(orig_blob, dest_blob)
176    return new_blob
177
178
179def create_blob(data, file_extension, binary=False):
180    """
181    Create a ZODB blob file from some data, return the blob object
182    """
183    blob = Blob()
184    blob.file_extension = file_extension
185    with blob.open('w') as open_blob:
186        # use .encode() to convert the string to bytes if needed
187        if not binary and not isinstance(data, bytes):
188            data = data.encode('utf-8')
189        open_blob.write(data)
190    return blob
191
192
193def save_map_screenshot(workout):
194    if workout.has_gpx:
195        current_path = os.path.abspath(os.path.dirname(__file__))
196        tool_path = os.path.join(current_path, '../bin/screenshot_map')
197
198        screenshots_path = os.path.join(
199            current_path, 'static/maps', str(workout.owner.uid))
200        if not os.path.exists(screenshots_path):
201            os.makedirs(screenshots_path)
202
203        screenshot_path = os.path.join(
204            screenshots_path, str(workout.workout_id))
205        screenshot_path += '.png'
206
207        subprocess.run(
208            [tool_path, str(workout.owner.uid), str(workout.workout_id),
209             screenshot_path], stdout=subprocess.PIPE, stderr=subprocess.PIPE)
210
211        return True
212
213    return False
Note: See TracBrowser for help on using the repository browser.