source: modules/mod_showOSD.py @ ec56a8a

Last change on this file since ec56a8a was ec56a8a, checked in by xkolman2 <xkolman2@…>, 4 years ago

replace active tracklog index by active tracklog path variable
-> fix the mess resulting from inconsistent indexes
make all modules load the tracklogs throught a method, not directly
-> safer, more transparent

git-svn-id: https://nlp.fi.muni.cz/svn/gps_navigace/trunk@156 0858a4d0-ffff-46e5-938e-62b5ecb34222

  • Property mode set to 100644
File size: 14.8 KB
Line 
1#!/usr/bin/python
2#----------------------------------------------------------------------------
3# Draw OSD (On Screen Display).
4#----------------------------------------------------------------------------
5# Copyright 2010, Martin Kolman
6#
7# This program is free software: you can redistribute it and/or modify
8# it under the terms of the GNU General Public License as published by
9# the Free Software Foundation, either version 3 of the License, or
10# (at your option) any later version.
11#
12# This program is distributed in the hope that it will be useful,
13# but WITHOUT ANY WARRANTY; without even the implied warranty of
14# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
15# GNU General Public License for more details.
16#
17# You should have received a copy of the GNU General Public License
18# along with this program.  If not, see <http://www.gnu.org/licenses/>.
19#---------------------------------------------------------------------------
20from base_module import ranaModule
21import time
22import gtk
23import pycha.line
24import cairo
25import geo
26
27def getModule(m,d):
28  return(showOSD(m,d))
29
30class showOSD(ranaModule):
31  """Draw OSD (On Screen Display)."""
32 
33  def __init__(self, m, d):
34    ranaModule.__init__(self, m, d)
35    self.items = None
36    self.routeProfileData = None
37    self.nearestPoint = None
38    self.nearestIndex = None
39    self.distanceList = None # sorted distances from current position
40#    self.avail = set(
41#                      'speed'
42#                      )
43   
44  def update(self):
45    # Get and set functions are used to access global data
46    self.set('num_updates', self.get('num_updates', 0) + 1)
47    #print "Updated %d times" % (self.get('num_updates'))
48
49  def drawScreenOverlay(self, cr):
50    """ draw currenty active information widgets TODO: just draw object from list"""
51    if self.m.get('config', {}):
52      config = self.m.get('config', None).userConfig
53
54      mode = self.get('mode', None)
55      if mode == None:
56        return
57
58      if mode not in config:
59        return
60      if 'OSD' in config[mode]:
61        items = config[mode]['OSD']
62        # we dont know the nearest poin when refreshing the screen
63        # TODO: dont search for nearest point on every refresh ?
64        self.nearestPoint = None
65        self.nearestIndex = None
66        self.distanceList = None
67        for item in items:
68          self.drawWidget(cr,items[item],item)
69
70
71  def drawWidget(self, cr, item, type):
72    if type == 'speed':
73      speed = self.get('speed', 0)
74      units = self.m.get('units', None)
75      if speed == None:
76        speedString = "? %s" % units.currentUnitString()
77      else:
78        speedString = units.km2CurrentUnitPerHourString(speed)
79      self.drawMultilineTextWidget(cr, item, speedString)
80    elif type == 'time':
81      timeString = time.strftime("%H:%M")
82      self.drawMultilineTextWidget(cr, item, timeString)
83    elif type == 'coordinates':
84      pos = self.get('pos', None)
85      if pos == None:
86        return
87      posString = "%f(lat)|%f(lon)" % pos
88      self.drawMultilineTextWidget(cr, item, posString)
89    elif type == 'statistics':
90      units = self.m.get('units', None)
91      maxSpeed = self.get('maxSpeed', 0)
92      avg = self.get('avgSpeed', 0)
93      statString = "max:%s|" % units.km2CurrentUnitPerHourStringTwoDP(maxSpeed)
94      statString+= "avg:%s" % units.km2CurrentUnitPerHourStringTwoDP(avg)
95      self.drawMultilineTextWidget(cr, item, statString)
96    elif type == 'route_profile':
97      self.drawRouteProfile(cr, item)
98    elif type == 'time_to_start':
99      if self.routeProfileData == None:
100        text = "activate a track | to show time to start"
101        self.drawMultilineTextWidget(cr, item, text)
102        return
103      else:
104        if self.distanceList == None: # was the neerest point already determined ?
105          if self.findNearestPoint() == False: # False is returned if the nearest point can not be computed
106            return
107      avgSpeed = self.get('avgSpeed', 0)
108      units = self.m.get('units', None) # get the unit conversion module
109      avgSpeedMPS = units.currentSpeedUnitToMS(avgSpeed)
110      currentLength = (self.routeProfileData[self.nearestIndex][0])*1000.0 # rem. length in meters
111      if avgSpeedMPS == 0 or avgSpeedMPS == 0.0:
112        timeString = "start moving"
113      else:
114        timeString = self.timeString(currentLength/avgSpeedMPS)
115      text = 'to start:|%s h:m' % timeString
116      self.drawMultilineTextWidget(cr, item, text)
117    elif type == 'time_to_destination':
118      if self.routeProfileData == None:
119        text = "activate a track | to show time to dest."
120        self.drawMultilineTextWidget(cr, item, text)
121        return
122      else:
123        if self.distanceList == None: # was the neerest point already determined ?
124          if self.findNearestPoint() == False: # False is returned if the nearest point can not be computed
125            return
126      avgSpeed = self.get('avgSpeed', 0)
127      units = self.m.get('units', None) # get the unit conversion module
128      avgSpeedMPS = units.currentSpeedUnitToMS(avgSpeed)
129      currentLength = self.routeProfileData[self.nearestIndex][0]
130      remainingLength = (self.routeProfileData[-1][0] - currentLength)*1000 # rem. length in meters
131      if avgSpeedMPS == 0 or avgSpeedMPS == 0.0:
132        timeString = "start moving"
133      else:
134        timeString = self.timeString(remainingLength/avgSpeedMPS)
135      text = 'to destination|%s h:m' % timeString
136      self.drawMultilineTextWidget(cr, item, text)
137    elif type == 'route_remaining_length':
138      if self.routeProfileData == None:
139        text = "activate a track | to show rem. length"
140        self.drawMultilineTextWidget(cr, item, text)
141        return
142      else:
143        if self.distanceList == None: # was the neerest point already determined ?
144          if self.findNearestPoint() == False: # False is returned if the nearest point can not be computed
145            return
146        currentLength = self.routeProfileData[self.nearestIndex][0]
147        remainingLength = self.routeProfileData[-1][0] - currentLength #TODO: from actual current position
148      units = self.m.get('units', None) # get the unit conversion module
149      text = "%s from start|" % units.km2CurrentUnitPerHourStringTwoDP(currentLength)
150      text+= "%s to destination" % units.km2CurrentUnitPerHourStringTwoDP(remainingLength)
151      self.drawMultilineTextWidget(cr, item, text)
152
153  def timeString(self, seconds):
154    """convert seconds to h:m:s string"""
155    minutes,seconds = divmod(seconds, 60)
156    hours, minutes = divmod(minutes, 60)
157    return '%02d:%02d' % (hours,minutes)
158
159  def drawMultilineTextWidget(self,cr ,item ,text=""):
160    if 'px' and 'py' in item:
161      proj = self.m.get('projection', None)
162      (px,py) = float(item['px']), float(item['py'])
163      (x, y) = proj.screenPos(px,py)
164
165      if 'font_size' in item:
166        fontSize = int(item['font_size'])
167      else:
168        fontSize = 30
169      cr.set_font_size(fontSize)
170
171#      if 'pw' and 'ph' in item: # are the width and height set ?
172#        w = proj.screenWidth(float(item['pw']))
173#        h = proj.screenHeight(float(item['ph']))
174#      else: # width and height are not set, we ge them from the text size
175#        extents = cr.text_extents(text)
176#        (w,h) = (extents[2], extents[3])
177
178      lines  = text.split('|')
179      border = 10
180      (w,h) = (0,0)
181      yOffset = []
182      for line in lines:
183        extents = cr.text_extents(line)
184        (w,h) = (max(w,extents[2]), h + extents[3] + border)
185        yOffset.append(y+h)
186
187      if 'align' in item:
188        if item['align'] == 'right':
189          x = x - w
190
191
192
193      self.drawBackground(cr, x, y, w, h)
194     
195      i = 0
196      for line in lines:
197        self.drawText(cr, x, yOffset[i], line)
198        i = i + 1
199
200  def drawBackground(self, cr, x, y, w, h, source=None):
201    cr.set_line_width(2)
202    cr.set_source_rgba(0, 0, 1, 0.45) # trasparent blue
203#    (rx,ry,rw,rh) = (x, y-h*1.4, w*1.2, (h*2))
204    (rx,ry,rw,rh) = (x, y, w*1.2, h*1.2)
205    cr.rectangle(rx,ry,rw,rh) # create the transparent background rectangle
206    cr.fill()
207
208  def drawBackgroundExact(self, cr, x, y, w, h, source=None):
209    cr.set_line_width(2)
210    cr.set_source_rgba(0, 0, 1, 0.45) # trasparent blue
211    (rx,ry,rw,rh) = (x, y, w, h)
212    cr.rectangle(rx,ry,rw,rh) # create the transparent background rectangle
213    cr.fill()
214
215  def drawText(self, cr, x, y, text, source=None):
216    cr.set_source_rgba(1, 1, 1, 0.95) # slightly transparent white
217    cr.move_to(x+10,y)
218    cr.show_text(text) # show the trasparent notification text
219    cr.stroke()
220    cr.fill()
221
222# from PyCha.color module
223
224  def findNearestPoint(self):
225    """find the nearest point of the active tracklog(nearest to our position)"""
226    pos = self.get('pos', None)
227    if pos == None:
228      return False
229#    print profile
230
231#    (sx,sy) = proj.screenPos(0.5, 0.5)
232#    (sLat,sLon) = proj.xy2ll(sx, sy)
233
234    (pLat,pLon) = pos
235
236    # list order: distance from pos/screen center, lat, lon, distance from start, elevation
237    distList = [(geo.distance(pLat,pLon,i[2],i[3]),i[2],i[3],i[0],i[1]) for i in self.routeProfileData]
238#    distList.sort()
239
240    l = [k[0] for k in distList] # make a list with only distances to our position
241
242
243    self.nearestIndex = l.index(min(l)) # get index of the shortest distance
244    self.nearestPoint = distList[self.nearestIndex] # get the nearest point
245    self.distanceList = distList
246    return True
247
248
249  def drawRouteProfile(self, cr, item):
250    """draw a dynamic route profile as a part of the osd"""
251    if self.routeProfileData == None:
252      text = "activate a tracklog|to show route profile"
253      item['font_size'] = 20
254      self.drawMultilineTextWidget(cr, item, text)
255      return
256
257#    profile = self.routeProfileData
258#    print item
259
260    proj = self.m.get('projection', None)
261    (px,py) = float(item['px']), float(item['py'])
262    (x, y) = proj.screenPos(px,py)
263
264    if 'pw' and 'ph' in item:
265      (pw,ph) = float(item['pw']), float(item['ph'])
266    else:
267      (pw,ph) = (0.2,0.15)
268    (w, h) = proj.screenPos(pw,ph)
269
270    segmentLength = 5
271    if 'segment_length' in item:
272      segmentLength = float(item['segment_length'])
273
274    if self.distanceList == None: # was the neerest point already determined ?
275      if self.findNearestPoint() == False: # False is returned if the nearest point can not be computed
276        return
277
278    distList = self.distanceList
279    nearestIndex = self.nearestIndex # get index of the shortest distance
280#    nearestPoint = self.nearestPoint # get the nearest point
281
282    # * build the dataset *
283
284    # prepare
285
286    step = distList[1][3] # distance between the periodic points
287    totalLength = len(distList)
288
289    nrPoints = int(segmentLength / step) # how many points re in the range ?
290
291    leftAdd = 0
292    rightAdd = 0
293
294    leftIndex = nearestIndex - nrPoints/2
295    rightIndex = nearestIndex + nrPoints/2
296#    print leftAdd, leftIndex, nearestIndex, rightIndex, rightAdd
297    if leftIndex < 0:
298      leftAdd = abs(leftIndex)
299      leftIndex = 0
300    if rightIndex > totalLength-1:
301      rightAdd = rightIndex - (totalLength-1)
302      rightIndex = totalLength-1
303
304
305#    print totalLength
306#    print leftAdd, leftIndex, nearestIndex, rightIndex, rightAdd
307
308    # build
309
310    zeroes = [(0,0,0,0,0)] # simulates no data areas
311
312    profile = []
313
314    profile.extend(zeroes*leftAdd) # possible front padding
315    profile.extend(distList[leftIndex:rightIndex]) # add the current segment
316    profile.extend(zeroes*rightAdd) # possible end padding
317
318#    mostDistantPoint = distList[-1]
319
320#    (nx,ny) = proj.ll2xy(nearestPoint[1],nearestPoint[2])
321#    cr.set_source_rgb(1,1,0)
322#    cr.rectangle(nx,ny,20,20)
323#    cr.stroke()
324#    cr.fill()
325
326    minimum = min(map(lambda x: x[4], profile))
327    maximum = max(map(lambda x: x[4], profile))
328
329    self.drawBackgroundExact(cr, x, y, w, h)
330    self.drawLinechart(cr, x, y, w, 0.8*h, maximum, minimum, profile)
331
332
333    # draw current elevation indicator
334    cr.set_source_rgb(1,1,0)
335    cr.set_line_width(3)
336    cr.move_to(x+w/2.0,y)
337    cr.line_to(x+w/2.0,y+h)
338    cr.stroke()
339    cr.fill()
340
341    cr.set_source_rgb(1,1,1)
342    lengthString = '%1.2f km' % segmentLength
343    maxString = "max: %1.0f m" % maximum
344    minString = "min: %1.0f m" % minimum
345    current = "%1.0f m" % self.nearestPoint[4]
346
347    menu = self.m.get('menu', None) # we use a function from this module to draw text
348    menu.drawText(cr, maxString, x, y, w*0.35, 0.15*h)
349    menu.drawText(cr, minString, x+0.63*w, y, w*0.35, 0.15*h)
350    menu.drawText(cr, lengthString, x, y+0.8*h, w*0.45, 0.15*h)
351    cr.set_source_rgb(1,1,0)
352    menu.drawText(cr, current, x+w/1.9, y+0.8*h, w*0.3, 0.15*h)
353
354
355
356  def drawLinechart(self, cr, x, y, w, h, maximum, minimum,profile):
357    """draw a linechart, showing a segment of the route"""
358
359    w = int(w)
360    h = int(h)
361
362    surface = cairo.ImageSurface(cairo.FORMAT_ARGB32, w, h)
363    list = profile
364
365#    minimum = min(map(lambda x: x[4], list))
366#    maximum = max(map(lambda x: x[4], list))
367
368    lines = tuple(map(lambda x: (x[3], x[4]), list))
369
370    dataSet = (
371        ('lines', [(i, l[1]) for i, l in enumerate(lines)]),
372        )
373
374    options = {
375        'axis': {
376
377            'lineWidth':1,
378            'lineColor':'#0000ff',
379            'labelFontSize': 12,
380
381            'x': {
382            },
383            'y': {
384                'range' : (minimum-h/20,maximum+h/20),
385#                'range' : (minimum,maximum),
386            }
387        },
388        'background': {
389            'hide':True,
390            'color': '#eeeeff',
391#            'lineColor': '#444444'
392            'lineColor': '#eeeeff',
393            'lineWidth':10
394        },
395        'colorScheme': {
396            'name': 'gradient',
397            'args': {
398#                'initialColor': 'blue',
399                'initialColor': '#eeeeff',
400            },
401        },
402        'legend': {
403            'hide': True,
404        },
405        'stroke': {
406            'hide': False,
407            'color':'#eeeeff',
408            'width':3
409        },
410        'yvals': {
411            'hide': True,
412            'color':'#eeeeff',
413        },
414        'xvals': {
415            'hide': True,
416            'color':'#eeeeff',
417        },
418        'padding': {
419            'left': 0,
420            'right': 0,
421            'bottom': 0,
422        },
423        'shouldFill':False,
424        'lineWidth':10,
425    }
426    chart = pycha.line.LineChart(surface, options)
427    chart.addDataset(dataSet)
428    chart.render()
429    cr.set_source_surface(surface, x, y)
430    cr.paint()
431
432
433
434def parseColour(self, inputString):
435  color = gtk.gdk.color_parse(inputString)
436  return color
437
438def hex2rgb(hexstring, digits=2):
439    """Converts a hexstring color to a rgb tuple.
440
441    Example: #ff0000 -> (1.0, 0.0, 0.0)
442
443    digits is an integer number telling how many characters should be
444    interpreted for each component in the hexstring.
445    """
446    if isinstance(hexstring, (tuple, list)):
447        return hexstring
448
449    top = float(int(digits * 'f', 16))
450    r = int(hexstring[1:digits+1], 16)
451    g = int(hexstring[digits+1:digits*2+1], 16)
452    b = int(hexstring[digits*2+1:digits*3+1], 16)
453    return r / top, g / top, b / top
454
455if(__name__ == "__main__"):
456  a = example({}, {})
457  a.update()
458  a.update()
459  a.update()
Note: See TracBrowser for help on using the repository browser.