Example #1
0
    def clusterTrackpoints(self, trackpointsList, cluster_distance):
        """
        Groups points that are less than cluster_distance pixels apart at
        a given zoom level into a cluster.
        """
        points = [{'latitude': point.latitude, 'longitude': point.longitude} for point in trackpointsList[0]]
        self.set('clPoints', points)
        clusters = []
        while len(points) > 0:
            point1 = points.pop()
            cluster = []
            for point2 in points[:]:

                pixel_distance = geo.distance(point1['latitude'],
                                              point1['longitude'],
                                              point2['latitude'],
                                              point2['longitude'])

                if pixel_distance < cluster_distance:
                    points.remove(point2)
                    cluster.append(point2)

            # add the first point to the cluster
            if len(cluster) > 0:
                cluster.append(point1)
                clusters.append(cluster)
            else:
                clusters.append([point1])

        return clusters
Example #2
0
    def from_monav_result(cls, result):
        """Convert route nodes from the Monav routing result"""
        # to (lat, lon) tuples
        if result:
            # route points
            routePoints = []
            mLength = 0 # in meters
            if result.nodes:
                firstNode = result.nodes[0]
                prevLat, prevLon = firstNode.lat, firstNode.lon
                # there is one from first to first calculation on start,
                # but as it should return 0, it should not be an issue
                for node in result.nodes:
                    routePoints.append((node.lat, node.lon, None))
                    mLength += geo.distance(prevLat, prevLon, node.lat, node.lon) * 1000
                    prevLat, prevLon = node.lat, node.lon

            way = cls(routePoints)
            way.duration = result.seconds
            way._setLength(mLength)

            # detect turns
            messagePoints = detect_monav_turns(result)
            way.add_message_points(messagePoints)
            return way
        else:
            return None
Example #3
0
 def from_handmade(cls, start, middlePoints, destination):
     """Convert hand-made route data to a way """
     if start and destination:
         # route points & message points are generated at once
         # * empty string as message => no message point, just route point
         routePoints = [(start[0], start[1], None)]
         messagePoints = []
         mLength = 0 # in meters
         lastLat, lastLon = start[0], start[1]
         for point in middlePoints:
             lat, lon, elevation, message = point
             mLength += geo.distance(lastLat, lastLon, lat, lon) * 1000
             routePoints.append((lat, lon, elevation))
             if message != "": # is it a message point ?
                 point = TurnByTurnPoint(lat, lon, elevation, message)
                 point.distanceFromStart = mLength
                 messagePoints.append(point)
             lastLat, lastLon = lat, lon
         routePoints.append((destination[0], destination[1], None))
         way = cls(routePoints)
         way.add_message_points(messagePoints)
         # huge guestimation (avg speed 60 km/h = 16.7 m/s)
         seconds = mLength / 16.7
         way.duration = seconds
         way._setLength(mLength)
         # done, return the result
         return way
     else:
         return None
Example #4
0
    def findNearestPoint(self):
        """find the nearest point of the active tracklog(nearest to our position)"""
        pos = self.get('pos', None)
        if pos is None:
            return False
        #    self.log.debug(profile)

        #    (sx,sy) = proj.screenPos(0.5, 0.5)
        #    (sLat,sLon) = proj.xy2ll(sx, sy)

        (pLat, pLon) = pos

        # list order: distance from pos/screen center, lat, lon, distance from start, elevation
        distList = [(geo.distance(pLat, pLon, i[2],
                                  i[3]), i[2], i[3], i[0], i[1])
                    for i in self.routeProfileData]
        #    distList.sort()

        l = [k[0] for k in distList
             ]  # make a list with only distances to our position

        self.nearestIndex = l.index(
            min(l))  # get index of the shortest distance
        self.nearestPoint = distList[
            self.nearestIndex]  # get the nearest point
        self.distanceList = distList
        return True
Example #5
0
def fromMonavResult(result, getTurns=None):
    """convert route nodes from the Monav routing result"""
    # to (lat, lon) tuples
    if result:
        # route points
        routePoints = []
        mLength = 0 # in meters
        if result.nodes:
            firstNode = result.nodes[0]
            prevLat, prevLon = firstNode.latitude, firstNode.longitude
            # there is one from first to first calculation on start,
            # but as it should return 0, it should not be an issue
            for node in result.nodes:
                routePoints.append((node.latitude, node.longitude, None))
                mLength += geo.distance(prevLat, prevLon, node.latitude, node.longitude) * 1000
                prevLat, prevLon = node.latitude, node.longitude

        way = Way(routePoints)
        way.setDuration(result.seconds)
        way._setLength(mLength)

        # was a directions generation method provided ?
        if getTurns:
            # generate directions
            messagePoints = getTurns(result)
            way.addMessagePoints(messagePoints)
        return way
    else:
        return None
Example #6
0
 def from_handmade(cls, start, middlePoints, destination):
     """Convert hand-made route data to a way """
     if start and destination:
         # route points & message points are generated at once
         # * empty string as message => no message point, just route point
         routePoints = [(start[0], start[1], None)]
         messagePoints = []
         mLength = 0 # in meters
         lastLat, lastLon = start[0], start[1]
         for point in middlePoints:
             lat, lon, elevation, message = point
             mLength += geo.distance(lastLat, lastLon, lat, lon) * 1000
             routePoints.append((lat, lon, elevation))
             if message != "": # is it a message point ?
                 point = TurnByTurnPoint(lat, lon, elevation, message)
                 point.distanceFromStart = mLength
                 messagePoints.append(point)
             lastLat, lastLon = lat, lon
         routePoints.append((destination[0], destination[1], None))
         way = cls(routePoints)
         way.add_message_points(messagePoints)
         # huge guestimation (avg speed 60 km/h = 16.7 m/s)
         seconds = mLength / 16.7
         way.duration = seconds
         way._setLength(mLength)
         # done, return the result
         return way
     else:
         return None
Example #7
0
    def from_monav_result(cls, result):
        """Convert route nodes from the Monav routing result"""
        # to (lat, lon) tuples
        if result:
            # route points
            routePoints = []
            mLength = 0 # in meters
            if result.nodes:
                firstNode = result.nodes[0]
                prevLat, prevLon = firstNode.latitude, firstNode.longitude
                # there is one from first to first calculation on start,
                # but as it should return 0, it should not be an issue
                for node in result.nodes:
                    routePoints.append((node.latitude, node.longitude, None))
                    mLength += geo.distance(prevLat, prevLon, node.latitude, node.longitude) * 1000
                    prevLat, prevLon = node.latitude, node.longitude

            way = cls(routePoints)
            way.duration = result.seconds
            way._setLength(mLength)

            # detect turns
            messagePoints = detect_monav_turns(result)
            way.add_message_points(messagePoints)
            return way
        else:
            return None
Example #8
0
def fromMonavResult(result, getTurns=None):
    """convert route nodes from the Monav routing result"""
    # to (lat, lon) tuples
    if result:
        # route points
        routePoints = []
        mLength = 0  # in meters
        if result.nodes:
            firstNode = result.nodes[0]
            prevLat, prevLon = firstNode.latitude, firstNode.longitude
            # there is one from first to first calculation on start,
            # but as it should return 0, it should not be an issue
            for node in result.nodes:
                routePoints.append((node.latitude, node.longitude, None))
                mLength += geo.distance(prevLat, prevLon, node.latitude,
                                        node.longitude) * 1000
                prevLat, prevLon = node.latitude, node.longitude

        way = Way(routePoints)
        way.setDuration(result.seconds)
        way._setLength(mLength)

        # was a directions generation method provided ?
        if getTurns:
            # generate directions
            messagePoints = getTurns(result)
            way.addMessagePoints(messagePoints)
        return way
    else:
        return None
Example #9
0
    def cluster_trackpoints(self, trackpointsList, cluster_distance):
        """
        Groups points that are less than cluster_distance pixels apart at
        a given zoom level into a cluster.
        """
        points = [{'latitude': point.latitude, 'longitude': point.longitude} for point in trackpointsList[0]]
        self.set('clPoints', points)
        clusters = []
        while len(points) > 0:
            point1 = points.pop()
            cluster = []
            for point2 in points[:]:

                pixel_distance = geo.distance(point1['latitude'],
                                              point1['longitude'],
                                              point2['latitude'],
                                              point2['longitude'])

                if pixel_distance < cluster_distance:
                    points.remove(point2)
                    cluster.append(point2)

            # add the first point to the cluster
            if len(cluster) > 0:
                cluster.append(point1)
                clusters.append(cluster)
            else:
                clusters.append([point1])

        return clusters
Example #10
0
    def _drawPoint(self, crPosUnitsProj, point, colors, distance=True, action="", highlight=False):
        (cr, pos, units, proj) = crPosUnitsProj
        (lat, lon) = point.getLL()  # TODO use getLLE for 3D distance
        (lat1, lon1) = pos  # current position coordinates
        kiloMetricDistance = geo.distance(lat, lon, lat1, lon1)
        unitString = units.km2CurrentUnitString(kiloMetricDistance, 0, True)
        if distance and pos and units:
            distanceString = " (%s)" % unitString
        else:
            distanceString = ""

        text = "%s%s" % (point.name, distanceString)

        (x, y) = proj.ll2xy(lat, lon)
        # get colors
        (bgColor, textColor) = colors

        if highlight:
            # draw the highlighting circle
            cr.set_line_width(8)
            cr.set_source_rgba(*bgColor.getCairoColor())  # highlight circle color
            cr.arc(x, y, 15, 0, 2.0 * math.pi)
            cr.stroke()
            cr.fill()

        # draw the point
        cr.set_source_rgb(0.0, 0.0, 0.0)
        cr.set_line_width(10)
        cr.arc(x, y, 3, 0, 2.0 * math.pi)
        cr.stroke()
        cr.set_source_rgb(0.0, 0.0, 1.0)
        cr.set_line_width(8)
        cr.arc(x, y, 2, 0, 2.0 * math.pi)
        cr.stroke()

        # draw a caption with transparent background
        cr.set_font_size(25)
        extents = cr.text_extents(text)  # get the text extents
        (w, h) = (extents[2], extents[3])
        border = 2
        cr.set_line_width(2)

        cr.set_source_rgba(*bgColor.getCairoColor())  # trasparent blue
        (rx, ry, rw, rh) = (x - border + 12, y + border + h * 0.2 + 6, w + 4 * border, -(h * 1.4))
        cr.rectangle(rx, ry, rw, rh)  # create the transparent background rectangle
        cr.fill()

        # register clickable area
        click = self.m.get("clickHandler", None)
        if click:
            # make the POI caption clickable
            click.registerXYWH(rx, ry - (-rh), rw, -rh, action)
        cr.fill()

        # draw the actual text
        cr.set_source_rgba(*textColor.getCairoColor())  # slightly transparent white
        cr.move_to(x + 15, y + 7)
        cr.show_text(text)  # show the transparent result caption
        cr.stroke()
Example #11
0
    def drawMenu(self, cr, menuName, args=None):
        if menuName == 'infoAbout':
            menus = self.m.get('menu', None)
            if menus:
                button1 = ('Discussion', 'generic',
                           "ms:menu:open_url:%s" % self.discussions[0][0])
                button2 = ('Donate', 'generic',
                           "ms:menu:open_url:%s" % self.pay_pal_url)
                web = " <u>www.modrana.org</u> "
                email = " [email protected] "
                text = "modRana version:\n\n%s\n\n\n\nFor questions or feedback,\n\ncontact the <b>modRana</b> project:\n\n%s\n\n%s\n\n" % (
                    self.versionString, web, email)
                box = (text, "ms:menu:open_url:http://www.modrana.org")
                menus.drawThreePlusOneMenu(cr, 'infoAbout', 'set:menu:info',
                                           button1, button2, box)
        elif menuName == "infoDirection":
            menus = self.m.get('menu', None)
            if menus:
                button1 = ('set point', 'generic', "info:setPoint")
                button2 = ('clear', 'generic', "info:clearPoint")
                boxText = "no point selected"
                if self._dirPoint:
                    lat = self._dirPoint.lat
                    lon = self._dirPoint.lon
                    boxText = "lat: <b>%f</b> lon: <b>%f</b>" % (lat, lon)
                    # if possible, add distance information
                    units = self.m.get("units", None)
                    pos = self.get("pos", None)
                    if pos and units:
                        lat1, lon1 = pos
                        distance = geo.distance(lat, lon, lat1, lon1) * 1000
                        distance, shortUnit, longUnit = units.humanRound(
                            distance)
                        boxText += " %s %s" % (str(distance), shortUnit)
                box = (boxText, "")
                # draw the buttons and main box background
                menus.drawThreePlusOneMenu(cr, 'infoDirection',
                                           'set:menu:info', button1, button2,
                                           box)
                # get coordinates for the box
                (e1, e2, e3, e4, alloc) = menus.threePlusOneMenuCoords()
                # upper left corner XY
                (x4, y4) = e4
                # width and height
                (w, h, dx, dy) = alloc
                w4 = w - x4
                h4 = h - y4

                # draw the direction indicator
                axisX = x4 + w4 / 2.0
                axisY = y4 + h4 / 2.0
                # shortest side
                shortestSide = min(w4, h4)
                # usable side - 10% border
                side = shortestSide * 0.7

                angle = self._getDirectionAngle()

                self._drawDirectionIndicator(cr, axisX, axisY, side, angle)
Example #12
0
 def localAddPointsToLine(lat1, lon1, lat2, lon2, maxDistance):
     distance = geo.distance(lat1, lon1, lat2, lon2)
     if distance <= maxDistance: # the termination criterion
         return
     else:
         middleLat = (lat1 + lat2) / 2.0 # fin the midpoint between the two points
         middleLon = (lon1 + lon2) / 2.0
         pointsBetween.append((middleLat, middleLon))
         # process the 2 new line segments
         localAddPointsToLine(lat1, lon1, middleLat, middleLon, maxDistance)
         localAddPointsToLine(middleLat, middleLon, lat2, lon2, maxDistance)
Example #13
0
 def localAddPointsToLine(lat1, lon1, lat2, lon2, maxDistance):
     distance = geo.distance(lat1, lon1, lat2, lon2)
     if distance <= maxDistance:  # the termination criterion
         return
     else:
         middleLat = (lat1 + lat2) / 2.0  # fin the midpoint between the two points
         middleLon = (lon1 + lon2) / 2.0
         pointsBetween.append((middleLat, middleLon))
         # process the 2 new line segments
         localAddPointsToLine(lat1, lon1, middleLat, middleLon, maxDistance)
         localAddPointsToLine(middleLat, middleLon, lat2, lon2, maxDistance)
Example #14
0
    def drawMenu(self, cr, menuName, args=None):
        if menuName == 'infoAbout':
            menus = self.m.get('menu', None)
            if menus:
                button1 = ('Discussion', 'generic', "ms:menu:openUrl:%s" % self.getDiscussionUrls()[0][0])
                button2 = ('Donate', 'generic', "ms:menu:openUrl:%s" % self.getPayPalUrl())
                web = " <u>www.modrana.org</u> "
                email = " [email protected] "
                text = "modRana version:\n\n%s\n\n\n\nFor questions or feedback,\n\ncontact the <b>modRana</b> project:\n\n%s\n\n%s\n\n" % (
                self.versionString, web, email)
                box = (text, "ms:menu:openUrl:http://www.modrana.org")
                menus.drawThreePlusOneMenu(cr, 'infoAbout', 'set:menu:info', button1, button2, box)
        elif menuName == "infoDirection":
            menus = self.m.get('menu', None)
            if menus:
                button1 = ('set point', 'generic', "info:setPoint")
                button2 = ('clear', 'generic', "info:clearPoint")
                boxText = "no point selected"
                if self._dirPoint:
                    lat = self._dirPoint.lat
                    lon = self._dirPoint.lon
                    boxText= "lat: <b>%f</b> lon: <b>%f</b>" % (lat, lon)
                    # if possible, add distance information
                    units = self.m.get("units", None)
                    pos = self.get("pos", None)
                    if pos and units:
                        lat1, lon1 = pos
                        distance = geo.distance(lat, lon, lat1, lon1)*1000
                        distance, shortUnit, longUnit = units.humanRound(distance)
                        boxText+=" %s %s" % (str(distance), shortUnit)
                box = (boxText, "")
                # draw the buttons and main box background
                menus.drawThreePlusOneMenu(cr, 'infoDirection', 'set:menu:info', button1, button2, box)
                # get coordinates for the box
                (e1, e2, e3, e4, alloc) = menus.threePlusOneMenuCoords()
                # upper left corner XY
                (x4, y4) = e4
                # width and height
                (w, h, dx, dy) = alloc
                w4 = w - x4
                h4 = h - y4

                # draw the direction indicator
                axisX = x4 + w4/2.0
                axisY = y4 + h4/2.0
                # shortest side
                shortestSide = min(w4,h4)
                # usable side - 10% border
                side = shortestSide*0.7

                angle = self._getDirectionAngle()

                self._drawDirectionIndicator(cr, axisX, axisY, side, angle)
Example #15
0
    def getClosestStep(self):
        """get the geographically closest step"""
        proj = self.m.get('projection', None) # we also need the projection module
        pos = self.get('pos', None) # and current position
        if pos and proj:
            (lat1, lon1) = pos
            tempSteps = self.route.getMessagePoints()
            for step in tempSteps:
                (lat2, lon2) = step.getLL()
                step.setCurrentDistance = geo.distance(lat1, lon1, lat2, lon2) * 1000 # km to m
            closestStep = sorted(tempSteps, key=lambda x: x.getCurrentDistance())[0]

            return closestStep
Example #16
0
    def getClosestStep(self):
        """get the geographically closest step"""
        proj = self.m.get('projection', None) # we also need the projection module
        pos = self.get('pos', None) # and current position
        if pos and proj:
            (lat1, lon1) = pos
            tempSteps = self.route.message_points
            for step in tempSteps:
                (lat2, lon2) = step.getLL()
                step.currentDistance = geo.distance(lat1, lon1, lat2, lon2) * 1000 # km to m
            closestStep = sorted(tempSteps, key=lambda x: x.currentDistance)[0]

            return closestStep
Example #17
0
    def _updateLogCB(self):
        """add current position at the end of the log"""
        pos = self.get('pos', None)
        if pos and not self.loggingPaused:
            timestamp = geo.timestampUTC()
            lat, lon = pos
            elevation = self.get('elevation', None)
            self.log1.addPointLLET(lat, lon, elevation, timestamp)
            self.log2.addPointLLET(lat, lon, elevation, timestamp)

            # update statistics for the current log
            if self.loggingEnabled and not self.loggingPaused:
                # update the current log speed statistics
                currentSpeed = self.get('speed', None)
                if currentSpeed:
                    # max speed
                    if currentSpeed > self.maxSpeed:
                        self.maxSpeed = currentSpeed
                        # avg speed
                    self.avg1 += currentSpeed
                    self.avg2 += (time.time() - self.lastUpdateTimestamp)
                    self.avgSpeed = self.avg1 / self.avg2
                    self.lastUpdateTimestamp = time.time()

                # update traveled distance
                if self.lastCoords:
                    lLat, lLon = self.lastCoords
                    self.distance += geo.distance(lLat, lLon, lat, lon)
                    self.lastCoords = lat, lon

                # update the on-map trace
                if self.lastTracePoint:
                    lat1, lon1 = self.lastTracePoint
                    # check if the point is distant enough from the last added point
                    # (which was either the first point or also passed the test)
                    addToTrace = True
                    try:
                        addToTrace = geo.distanceApprox(
                            lat, lon, lat1,
                            lon1) * 1000 >= DONT_ADD_TO_TRACE_THRESHOLD
                    except Exception:
                        self.log.exception(
                            "measuring distance failed (yeah, really! :P), adding point anyway"
                        )

                    if addToTrace:
                        self._addLL2Trace(lat, lon)
                else:  # this is the first known log point, just add it
                    self._addLL2Trace(lat, lon)
        # done, trigger the tracklog updated signal
        self.tracklogUpdated()
Example #18
0
 def screenRadius(self):
     """Return the centerpoint and radius of a circle encompassing the screen."""
     (centreXpixel, centreYpixel) = self.screenPos(0.5, 0.5)
     (centreX, centreY) = self.xy2ll(centreXpixel, centreYpixel)
     #ASUMPTION: screen is rectangular
     (cornerXpixel, cornerYpixel) = self.screenPos(0, 0) # we take the coordinates of one corner of the screen
     (cornerX, cornerY) = self.xy2ll(cornerXpixel, cornerYpixel) # we convert them to projection coordinates
     (anotherCornerXpixel, anotherCornerYpixel) = self.screenPos(1,
                                                                 1) # we take the coordinates of another corner of the screen
     (anotherCornerX, anotherCornerY) = self.xy2ll(anotherCornerXpixel,
                                                   anotherCornerYpixel) # we convert them to projection coordinates
     # radius = diagonal/2
     radius = geo.distance(anotherCornerX, anotherCornerY, cornerX, cornerY) / 2.0
     return centreX, centreY, radius # we return the centre coordinates and the radius
Example #19
0
    def _updateLogCB(self):
        """add current position at the end of the log"""
        pos = self.get("pos", None)
        if pos and not self.loggingPaused:
            timestamp = geo.timestampUTC()
            lat, lon = pos
            elevation = self.get("elevation", None)
            self.log1.addPointLLET(lat, lon, elevation, timestamp)
            self.log2.addPointLLET(lat, lon, elevation, timestamp)

            # update statistics for the current log
            if self.loggingEnabled and not self.loggingPaused:
                # update the current log speed statistics
                currentSpeed = self.get("speed", None)
                if currentSpeed:
                    # max speed
                    if currentSpeed > self.maxSpeed:
                        self.maxSpeed = currentSpeed
                        # avg speed
                    self.avg1 += currentSpeed
                    self.avg2 += time.time() - self.lastUpdateTimestamp
                    self.avgSpeed = self.avg1 / self.avg2
                    self.lastUpdateTimestamp = time.time()

                # update traveled distance
                if self.lastCoords:
                    lLat, lLon = self.lastCoords
                    self.distance += geo.distance(lLat, lLon, lat, lon)
                    self.lastCoords = lat, lon

                # update the on-map trace
                if self.lastTracePoint:
                    lat1, lon1 = self.lastTracePoint
                    # check if the point is distant enough from the last added point
                    # (which was either the first point or also passed the test)
                    addToTrace = True
                    try:
                        addToTrace = geo.distanceApprox(lat, lon, lat1, lon1) * 1000 >= DONT_ADD_TO_TRACE_THRESHOLD
                    except Exception:
                        import sys

                        e = sys.exc_info()[1]
                        print("tracklog: measuring distance failed (yeah, really! :P), adding point anyway")
                        print(e)
                    if addToTrace:
                        self._addLL2Trace(lat, lon)
                else:  # this is the first known log point, just add it
                    self._addLL2Trace(lat, lon)
Example #20
0
    def _get_closest_step(self):
        """Get the geographically closest step."""
        proj = self.m.get('projection',
                          None)  # we also need the projection module
        pos = self.get('pos', None)  # and current position
        if pos and proj:
            (lat1, lon1) = pos
            temp_steps = self._route.message_points
            for step in temp_steps:
                (lat2, lon2) = step.getLL()
                step.current_distance = geo.distance(lat1, lon1, lat2,
                                                     lon2) * 1000  # km to m
            closest_step = sorted(temp_steps,
                                  key=lambda x: x.current_distance)[0]

            return closest_step
Example #21
0
 def screenRadius(self):
     """Return the centerpoint and radius of a circle encompassing the screen."""
     (centreXpixel, centreYpixel) = self.screenPos(0.5, 0.5)
     (centreX, centreY) = self.xy2ll(centreXpixel, centreYpixel)
     #ASUMPTION: screen is rectangular
     (cornerXpixel, cornerYpixel) = self.screenPos(
         0, 0)  # we take the coordinates of one corner of the screen
     (cornerX, cornerY) = self.xy2ll(
         cornerXpixel,
         cornerYpixel)  # we convert them to projection coordinates
     (anotherCornerXpixel, anotherCornerYpixel) = self.screenPos(
         1, 1)  # we take the coordinates of another corner of the screen
     (anotherCornerX, anotherCornerY) = self.xy2ll(
         anotherCornerXpixel,
         anotherCornerYpixel)  # we convert them to projection coordinates
     # radius = diagonal/2
     radius = geo.distance(anotherCornerX, anotherCornerY, cornerX,
                           cornerY) / 2.0
     return centreX, centreY, radius  # we return the centre coordinates and the radius
Example #22
0
 def getTilesForRoute(self, route, radius, z):
     """get tilenames for tiles around the route for given radius and zoom"""
     # now we look whats the distance between each two trackpoints,
     # if it is larger than the tracklog radius, we add additional interpolated points,
     # so we get continuous coverage for the tracklog
     first = True
     interpolatedPoints = []
     for point in route:
         if first:  # the first point has no previous point
             (lastLat, lastLon) = point[0], point[1]
             first = False
             continue
         thisLat, thisLon = point[0], point[1]
         distBtwPoints = geo.distance(lastLat, lastLon, thisLat, thisLon)
         # if the distance between points was greater than the given radius for tiles,
         # there would be no continuous coverage for the route
         if distBtwPoints > radius:
             # so we call this recursive function to interpolate points between
             # points that are too far apart
             interpolatedPoints.extend(
                 self.addPointsToLine(lastLat, lastLon, thisLat, thisLon,
                                      radius))
         (lastLat, lastLon) = (thisLat, thisLon)
         # because we don't care about what order are the points in this case,
     # we just add the interpolated points to the end
     route.extend(interpolatedPoints)
     start = time.perf_counter()
     tilesToDownload = set()
     for point in route:  #now we iterate over all points of the route
         lat, lon = point[0], point[1]
         # be advised: the xy in this case are not screen coordinates but tile coordinates
         (x, y) = ll2xy(lat, lon, z)
         # the spiral gives us tiles around coordinates for a given radius
         currentPointTiles = self.spiral(x, y, z, radius)
         # now we take the resulting list  and process it in such a way,
         # that the tiles coordinates can be stored in a set,
         # so we will save only unique tiles
         outputSet = set(map(lambda x: tuple(x), currentPointTiles))
         tilesToDownload.update(outputSet)
     self.log.info("Listing tiles took %1.2f ms",
                   1000 * (time.perf_counter() - start))
     self.log.info("unique tiles %d", len(tilesToDownload))
     return tilesToDownload
Example #23
0
 def updateDistance(self):
     if self.localSearchResults:
         position = self.get("pos", None) # our lat lon coordinates
         resultList = []
         index = 0
         for point in self.localSearchResults: # we iterate over the local search results
             if position is not None:
                 (lat1, lon1) = position
                 (lat2, lon2) = point.getLL()
                 distance = geo.distance(lat1, lon1, lat2, lon2)
                 resultTuple = (distance, point, index)
                 resultList.append(resultTuple) # we pack each result into a tuple with ist distance from us
             else:
                 resultTuple = (
                 0, point, index) # in this case, we dont know our position, so we say the distance is 0
                 resultList.append(resultTuple)
             index += 1
         self.list = resultList
         return resultList
     else:
         self.log.error("error -> distance update on empty results")
Example #24
0
 def getTilesForRoute(self, route, radius, z):
     """get tilenames for tiles around the route for given radius and zoom"""
     # now we look whats the distance between each two trackpoints,
     # if it is larger than the tracklog radius, we add additional interpolated points,
     # so we get continuous coverage for the tracklog
     first = True
     interpolatedPoints = []
     for point in route:
         if first:  # the first point has no previous point
             (lastLat, lastLon) = point[0], point[1]
             first = False
             continue
         thisLat, thisLon = point[0], point[1]
         distBtwPoints = geo.distance(lastLat, lastLon, thisLat, thisLon)
         # if the distance between points was greater than the given radius for tiles,
         # there would be no continuous coverage for the route
         if distBtwPoints > radius:
             # so we call this recursive function to interpolate points between
             # points that are too far apart
             interpolatedPoints.extend(self.addPointsToLine(lastLat, lastLon, thisLat, thisLon, radius))
         (lastLat, lastLon) = (thisLat, thisLon)
         # because we don't care about what order are the points in this case,
     # we just add the interpolated points to the end
     route.extend(interpolatedPoints)
     start = clock()
     tilesToDownload = set()
     for point in route:  # now we iterate over all points of the route
         lat, lon = point[0], point[1]
         # be advised: the xy in this case are not screen coordinates but tile coordinates
         (x, y) = ll2xy(lat, lon, z)
         # the spiral gives us tiles around coordinates for a given radius
         currentPointTiles = self.spiral(x, y, z, radius)
         # now we take the resulting list  and process it in such a way,
         # that the tiles coordinates can be stored in a set,
         # so we will save only unique tiles
         outputSet = set(map(lambda x: tuple(x), currentPointTiles))
         tilesToDownload.update(outputSet)
     self.log.info("Listing tiles took %1.2f ms", 1000 * (clock() - start))
     self.log.info("unique tiles %d", len(tilesToDownload))
     return tilesToDownload
Example #25
0
    def _updateLogCB(self):
        """add current position at the end of the log"""
        pos = self.get('pos', None)
        if pos and not self.loggingPaused:
            timestamp = geo.timestampUTC()
            lat, lon = pos
            elevation = self.get('elevation', None)
            self.log1.addPointLLET(lat, lon, elevation, timestamp)
            self.log2.addPointLLET(lat, lon, elevation, timestamp)

            # update statistics for the current log
            if self.loggingEnabled and not self.loggingPaused:
                # update the current log speed statistics
                currentSpeed = self.get('speed', None)
                if currentSpeed:
                    # max speed
                    if currentSpeed > self.maxSpeed:
                        self.maxSpeed = currentSpeed
                        # avg speed
                    self.avg1 += currentSpeed
                    self.avg2 += (time.time() - self.lastUpdateTimestamp)
                    self.avgSpeed = self.avg1 / self.avg2
                    self.lastUpdateTimestamp = time.time()

                # update traveled distance
                if self.lastCoords:
                    lLat, lLon = self.lastCoords
                    self.distance += geo.distance(lLat, lLon, lat, lon)
                    self.lastCoords = lat, lon

                # update the on-map trace
                if self.lastTracePoint:
                    lat1, lon1 = self.lastTracePoint
                    # check if the point is distant enough from the last added point
                    # (which was either the first point or also passed the test)
                    if geo.distanceApprox(lat, lon, lat1, lon1) * 1000 >= DONT_ADD_TO_TRACE_THRESHOLD:
                        self._addLL2Trace(lat, lon)
                else: # this is the first known log point, just add it
                    self._addLL2Trace(lat, lon)
Example #26
0
    def findNearestPoint(self):
        """find the nearest point of the active tracklog(nearest to our position)"""
        pos = self.get('pos', None)
        if pos is None:
            return False
        #    self.log.debug(profile)

        #    (sx,sy) = proj.screenPos(0.5, 0.5)
        #    (sLat,sLon) = proj.xy2ll(sx, sy)

        (pLat, pLon) = pos

        # list order: distance from pos/screen center, lat, lon, distance from start, elevation
        distList = [(geo.distance(pLat, pLon, i[2], i[3]), i[2], i[3], i[0], i[1]) for i in self.routeProfileData]
        #    distList.sort()

        l = [k[0] for k in distList] # make a list with only distances to our position

        self.nearestIndex = l.index(min(l)) # get index of the shortest distance
        self.nearestPoint = distList[self.nearestIndex] # get the nearest point
        self.distanceList = distList
        return True
Example #27
0
 def updateDistance(self):
     if self.localSearchResults:
         position = self.get("pos", None)  # our lat lon coordinates
         resultList = []
         index = 0
         for point in self.localSearchResults:  # we iterate over the local search results
             if position is not None:
                 (lat1, lon1) = position
                 (lat2, lon2) = point.getLL()
                 distance = geo.distance(lat1, lon1, lat2, lon2)
                 resultTuple = (distance, point, index)
                 resultList.append(
                     resultTuple
                 )  # we pack each result into a tuple with ist distance from us
             else:
                 resultTuple = (
                     0, point, index
                 )  # in this case, we dont know our position, so we say the distance is 0
                 resultList.append(resultTuple)
             index += 1
         self.list = resultList
         return resultList
     else:
         self.log.error("error -> distance update on empty results")
Example #28
0
    def startTBT(self, fromWhere='first'):
        """start Turn-by-turn navigation"""

        # clean up any possible previous navigation data
        self.goToInitialState()

        # NOTE: turn and step are used interchangeably in the documentation
        m = self.m.get('route', None)
        if m:
            route = m.get_current_directions()
            if route: # is the route nonempty ?
                self.route = route
                # get route in radians for automatic rerouting
                self.radiansRoute = route.get_points_lle_radians(drop_elevation=True)
                # start rerouting watch
                self._startTBTWorker()

                # show the warning message
                self.sendMessage('ml:notification:m:use at own risk, watch for cliffs, etc.;3')
                # for some reason the combined distance does not account for the last step
                self.mRouteLength = route.length

                # some statistics
                metersPerSecSpeed = self.get('metersPerSecSpeed', None)
                dt = m.route_lookup_duration
                self.log.info("route lookup took: %f s" % dt)
                if dt and metersPerSecSpeed:
                    dm = dt * metersPerSecSpeed
                    self.log.info("distance traveled during lookup: %f m" % dm)
                    # the duration of the road lookup and other variables are currently not used
                # in the heuristics but might be added later to make the heuristics more robust


                # now we decide if we use the closest turn, or the next one,
                # as we might be already past it and on our way to the next turn
                cs = self.getClosestStep() # get geographically closest step
                pos = self.get('pos', None) # get current position
                pReachedDist = int(self.get('pointReachedDistance', 30)) # get the trigger distance
                nextTurnId = self.getStepID(cs) + 1
                nextStep = self.getStep(nextTurnId)
                # check if we have all the data needed for our heuristics
                self.log.info("trying to guess correct step to start navigation")
                if nextStep and pos and pReachedDist:
                    (lat, lon) = pos
                    (csLat, csLon) = cs.getLL()
                    (nsLat, nsLon) = nextStep.getLL()
                    pos2nextStep = geo.distance(lat, lon, nsLat, nsLon) * 1000
                    pos2currentStep = geo.distance(lat, lon, csLat, csLon) * 1000
                    currentStep2nextStep = geo.distance(csLat, csLon, nsLat, nsLon) * 1000
                    #          self.log.debug("pos",(lat,lon))
                    #          self.log.debug("cs",(csLat,csLon))
                    #          self.log.debug("ns",(nsLat,nsLon))
                    self.log.debug("position to next turn: %f m" % pos2nextStep)
                    self.log.debug("position to current turn: %f m" % pos2currentStep)
                    self.log.debug("current turn to next turn: %f m" % currentStep2nextStep)
                    self.log.debug("turn reached trigger distance: %f m" % pReachedDist)

                    if pos2currentStep > pReachedDist:
                        #this means we are out of the "capture circle" of the closest step

                        # what is more distant, the closest or the next step ?
                        if pos2nextStep < currentStep2nextStep:
                            # we are mostly probably already past the closest step,
                            # so we switch to the next step at once
                            self.log.debug("already past closest turn, switching to next turn")
                            self.setStepAsCurrent(nextStep)
                            # we play the message for the next step,
                            # with current distance to this step,
                            # to assure there is some voice output immediately after
                            # getting a new route or rerouting"""
                            plaintextMessage = nextStep.ssmlMessage
                            self.sayTurn(plaintextMessage, pos2nextStep)
                        else:
                            # we have probably not yet reached the closest step,
                            # so we start navigation from it
                            self.log.debug("closest turn not yet reached")
                            self.setStepAsCurrent(cs)

                    else:
                        # we are inside the  "capture circle" of the closest step,
                        # this means the navigation will trigger the voice message by itself
                        # and correctly switch to next step
                        # -> no need to switch to next step from here
                        self.log.debug("inside reach distance of closest turn")
                        self.setStepAsCurrent(cs)

                else:
                    # we dont have some of the data, that is needed to decide
                    # if we start the navigation from the closest step of from the step that is after it
                    # -> we just start from the closest step
                    self.log.debug("not enough data to decide, using closest turn")
                    self.setStepAsCurrent(cs)
        self._doNavigationUpdate() # run a first time navigation update
        self.locationWatchID = self.watch('locationUpdated', self.locationUpdateCB)
        self.log.info("started and ready")
Example #29
0
    def startTBT(self, fromWhere='first'):
        """start Turn-by-turn navigation"""

        # clean up any possible previous navigation data
        self.goToInitialState()

        # NOTE: turn and step are used interchangeably in the documentation
        m = self.m.get('route', None)
        if m:
            route = m.getCurrentDirections()
            if route: # is the route nonempty ?
                self.route = route
                # get route in radians for automatic rerouting
                self.radiansRoute = route.getPointsLLERadians(dropElevation=True)
                # start rerouting watch
                self._startTBTWorker()

                # show the warning message
                self.sendMessage('ml:notification:m:use at own risk, watch for cliffs, etc.;3')
                # for some reason the combined distance does not account for the last step
                self.mRouteLength = route.getLength()

                # some statistics
                metersPerSecSpeed = self.get('metersPerSecSpeed', None)
                dt = m.routeLookupDuration
                self.log.info("route lookup took: %f s" % dt)
                if dt and metersPerSecSpeed:
                    dm = dt * metersPerSecSpeed
                    self.log.info("distance traveled during lookup: %f m" % dm)
                    # the duration of the road lookup and other variables are currently not used
                # in the heuristics but might be added later to make the heuristics more robust


                # now we decide if we use the closest turn, or the next one,
                # as we might be already past it and on our way to the next turn
                cs = self.getClosestStep() # get geographically closest step
                pos = self.get('pos', None) # get current position
                pReachedDist = int(self.get('pointReachedDistance', 30)) # get the trigger distance
                nextTurnId = self.getStepID(cs) + 1
                nextStep = self.getStep(nextTurnId)
                # check if we have all the data needed for our heuristics
                self.log.info("trying to guess correct step to start navigation")
                if nextStep and pos and pReachedDist:
                    (lat, lon) = pos
                    (csLat, csLon) = cs.getLL()
                    (nsLat, nsLon) = nextStep.getLL()
                    pos2nextStep = geo.distance(lat, lon, nsLat, nsLon) * 1000
                    pos2currentStep = geo.distance(lat, lon, csLat, csLon) * 1000
                    currentStep2nextStep = geo.distance(csLat, csLon, nsLat, nsLon) * 1000
                    #          self.log.debug("pos",(lat,lon))
                    #          self.log.debug("cs",(csLat,csLon))
                    #          self.log.debug("ns",(nsLat,nsLon))
                    self.log.debug("position to next turn: %f m" % pos2nextStep)
                    self.log.debug("position to current turn: %f m" % pos2currentStep)
                    self.log.debug("current turn to next turn: %f m" % currentStep2nextStep)
                    self.log.debug("turn reached trigger distance: %f m" % pReachedDist)

                    if pos2currentStep > pReachedDist:
                        #this means we are out of the "capture circle" of the closest step

                        # what is more distant, the closest or the next step ?
                        if pos2nextStep < currentStep2nextStep:
                            # we are mostly probably already past the closest step,
                            # so we switch to the next step at once
                            self.log.debug("already past closest turn, switching to next turn")
                            self.setStepAsCurrent(nextStep)
                            # we play the message for the next step,
                            # with current distance to this step,
                            # to assure there is some voice output immediately after
                            # getting a new route or rerouting"""
                            plaintextMessage = nextStep.getSSMLMessage()
                            self.sayTurn(plaintextMessage, pos2nextStep)
                        else:
                            # we have probably not yet reached the closest step,
                            # so we start navigation from it
                            self.log.debug("closest turn not yet reached")
                            self.setStepAsCurrent(cs)

                    else:
                        # we are inside the  "capture circle" of the closest step,
                        # this means the navigation will trigger the voice message by itself
                        # and correctly switch to next step
                        # -> no need to switch to next step from here
                        self.log.debug("inside reach distance of closest turn")
                        self.setStepAsCurrent(cs)

                else:
                    # we dont have some of the data, that is needed to decide
                    # if we start the navigation from the closest step of from the step that is after it
                    # -> we just start from the closest step
                    self.log.debug("not enough data to decide, using closest turn")
                    self.setStepAsCurrent(cs)
        self._doNavigationUpdate() # run a first time navigation update
        self.locationWatchID = self.watch('locationUpdated', self.locationUpdateCB)
        self.log.info("started and ready")
Example #30
0
    def drawMapOverlay(self, cr):
        if self.expectPoint:
            self.makeMapClickable()
        if self.drawActivePOI:
            proj = self.m.get('projection', None)
            if proj and self.visiblePOI:
                for POI in self.visiblePOI:
                    poiID = POI.getId()
                    lat = POI.getLat()
                    lon = POI.getLon()
                    name = POI.getName()
                    hidePOICaptionZl = int(self.get("hideMarkerCaptionsBelowZl", 13))
                    if int(self.get('z', 15)) > hidePOICaptionZl:
                        distanceString = ""
                        pos = self.get('pos', None)
                        units = self.m.get('units', None)
                        if pos and units:
                            (lat1, lon1) = pos # current position coordinates
                            kiloMetricDistance = geo.distance(lat, lon, lat1, lon1)
                            unitString = units.km2CurrentUnitString(kiloMetricDistance, 0, True)
                            distanceString = " (%s)" % unitString

                        text = "" + name + distanceString

                    (x, y) = proj.ll2xy(lat, lon) #   draw the highlighting circle
                    cr.set_line_width(8)
                    cr.set_source_rgba(0.1, 0.6, 0.1, 0.55) # highlight circle color
                    cr.arc(x, y, 15, 0, 2.0 * math.pi)
                    cr.stroke()
                    cr.fill()

                    # draw the point
                    cr.set_source_rgb(0.0, 0.0, 0.0)
                    cr.set_line_width(10)
                    cr.arc(x, y, 3, 0, 2.0 * math.pi)
                    cr.stroke()
                    cr.set_source_rgb(0.0, 0.0, 1.0)
                    cr.set_line_width(8)
                    cr.arc(x, y, 2, 0, 2.0 * math.pi)
                    cr.stroke()

                    # draw a caption with transparent background
                    if int(self.get('z', 15)) > hidePOICaptionZl:
                        cr.set_font_size(25)
                        extents = cr.text_extents(text) # get the text extents
                        (w, h) = (extents[2], extents[3])
                        border = 2
                        cr.set_line_width(2)
                        cr.set_source_rgba(0.1, 0.6, 0.1, 0.45) # transparent blue
                        (rx, ry, rw, rh) = (x - border + 12, y + border + h * 0.2 + 6, w + 4 * border, -(h * 1.4))
                        cr.rectangle(rx, ry, rw, rh) # create the transparent background rectangle
                        cr.fill()

                        # register clickable area
                        click = self.m.get('clickHandler', None)
                        if click:
                            # make the POI caption clickable
                            if poiID is not None: # new POI have id == None
                                click.registerXYWH(rx, ry - (-rh), rw, -rh,
                                                   "ms:showPOI:setActivePOI:%d|set:menu:showPOI#POIDetail" % poiID)
                            else: # the last added POI is still set, no need to set the id
                                click.registerXYWH(rx, ry - (-rh), rw, -rh, "set:menu:showPOI#POIDetail")
                        cr.fill()

                        # draw the actual text
                        #        cr.set_source_rgba(1, 1, 0, 0.95) # slightly transparent white
                        cr.set_source_rgba(1, 1, 1, 0.95) # slightly transparent white
                        cr.move_to(x + 15, y + 7)
                        cr.show_text(text) # show the transparent result caption
                        cr.stroke()
Example #31
0
    def drawMapOverlay(self, cr):
        if self.expectPoint:
            self.makeMapClickable()
        if self.drawActivePOI:
            proj = self.m.get('projection', None)
            menus = self.m.get('menu', None)
            if proj and self.visiblePOI:
                for POI in self.visiblePOI:
                    poiID = POI.db_index
                    lat = POI.lat
                    lon = POI.lon
                    name = POI.name
                    hidePOICaptionZl = int(
                        self.get("hideMarkerCaptionsBelowZl", 13))
                    if int(self.get('z', 15)) > hidePOICaptionZl:
                        distanceString = ""
                        pos = self.get('pos', None)
                        units = self.m.get('units', None)
                        if pos and units:
                            (lat1, lon1) = pos  # current position coordinates
                            kiloMetricDistance = geo.distance(
                                lat, lon, lat1, lon1)
                            unitString = units.km2CurrentUnitString(
                                kiloMetricDistance, 0, True)
                            distanceString = " (%s)" % unitString

                        text = "" + name + distanceString

                    (x, y) = proj.ll2xy(lat,
                                        lon)  #   draw the highlighting circle
                    cr.set_line_width(8)
                    cr.set_source_rgba(0.1, 0.6, 0.1,
                                       0.55)  # highlight circle color
                    cr.arc(x, y, 15, 0, 2.0 * math.pi)
                    cr.stroke()
                    cr.fill()

                    # draw the point
                    cr.set_source_rgb(0.0, 0.0, 0.0)
                    cr.set_line_width(10)
                    cr.arc(x, y, 3, 0, 2.0 * math.pi)
                    cr.stroke()
                    cr.set_source_rgb(0.0, 0.0, 1.0)
                    cr.set_line_width(8)
                    cr.arc(x, y, 2, 0, 2.0 * math.pi)
                    cr.stroke()

                    # draw a caption with transparent background
                    if int(self.get('z', 15)) > hidePOICaptionZl:
                        cr.set_font_size(25)
                        extents = cr.text_extents(text)  # get the text extents
                        (w, h) = (extents[2], extents[3])
                        border = 2
                        cr.set_line_width(2)
                        cr.set_source_rgba(0.1, 0.6, 0.1,
                                           0.45)  # transparent blue
                        (rx, ry, rw,
                         rh) = (x - border + 12, y + border + h * 0.2 + 6,
                                w + 4 * border, -(h * 1.4))
                        cr.rectangle(
                            rx, ry, rw,
                            rh)  # create the transparent background rectangle
                        cr.fill()

                        # register clickable area
                        click = self.m.get('clickHandler', None)
                        if click:
                            # make the POI caption clickable
                            if poiID is not None:  # new POI have id == None
                                click.registerXYWH(
                                    rx, ry - (-rh), rw, -rh,
                                    "ms:showPOI:setActivePOI:%d|set:menu:showPOI#POIDetail"
                                    % poiID)
                            else:  # the last added POI is still set, no need to set the id
                                click.registerXYWH(
                                    rx, ry - (-rh), rw, -rh,
                                    "set:menu:showPOI#POIDetail")
                        cr.fill()

                        # draw the actual text
                        #        cr.set_source_rgba(1, 1, 0, 0.95) # slightly transparent white
                        cr.set_source_rgba(1, 1, 1,
                                           0.95)  # slightly transparent white
                        menus.drawText(cr, text, rx, ry - (-rh), rw, -rh, 0.05)
                        cr.stroke()
Example #32
0
    def _drawPoint(self,
                   crPosUnitsProj,
                   point,
                   colors,
                   distance=True,
                   action="",
                   highlight=False):
        (cr, pos, units, proj) = crPosUnitsProj
        (lat, lon) = point.getLL()  #TODO use getLLE for 3D distance
        (lat1, lon1) = pos  # current position coordinates
        kiloMetricDistance = geo.distance(lat, lon, lat1, lon1)
        unitString = units.km2CurrentUnitString(kiloMetricDistance, 0, True)
        if distance and pos and units:
            distanceString = " (%s)" % unitString
        else:
            distanceString = ""

        text = "%s%s" % (point.name, distanceString)

        (x, y) = proj.ll2xy(lat, lon)
        # get colors
        (bgColor, textColor) = colors

        if highlight:
            # draw the highlighting circle
            cr.set_line_width(8)
            cr.set_source_rgba(
                *bgColor.getCairoColor())  # highlight circle color
            cr.arc(x, y, 15, 0, 2.0 * math.pi)
            cr.stroke()
            cr.fill()

        # draw the point
        cr.set_source_rgb(0.0, 0.0, 0.0)
        cr.set_line_width(10)
        cr.arc(x, y, 3, 0, 2.0 * math.pi)
        cr.stroke()
        cr.set_source_rgb(0.0, 0.0, 1.0)
        cr.set_line_width(8)
        cr.arc(x, y, 2, 0, 2.0 * math.pi)
        cr.stroke()

        # draw a caption with transparent background
        cr.set_font_size(25)
        extents = cr.text_extents(text)  # get the text extents
        (w, h) = (extents[2], extents[3])
        border = 2
        cr.set_line_width(2)

        cr.set_source_rgba(*bgColor.getCairoColor())  # trasparent blue
        (rx, ry, rw, rh) = (x - border + 12, y + border + h * 0.2 + 6,
                            w + 4 * border, -(h * 1.4))
        cr.rectangle(rx, ry, rw,
                     rh)  # create the transparent background rectangle
        cr.fill()

        # register clickable area
        click = self.m.get('clickHandler', None)
        if click:
            # make the POI caption clickable
            click.registerXYWH(rx, ry - (-rh), rw, -rh, action)
        cr.fill()

        # draw the actual text
        cr.set_source_rgba(
            *textColor.getCairoColor())  # slightly transparent white
        cr.move_to(x + 15, y + 7)
        cr.show_text(text)  # show the transparent result caption
        cr.stroke()
Example #33
0
    def drawMenu(self, cr, menuName, args=None):
        # is this menu the correct menu ?
        if menuName != 'routeProfile':
            return # we aren't the active menu so we don't do anything
        (x1, y1, w, h) = self.get('viewport', None)
        #    if w > h:
        #      cols = 4
        #      rows = 3
        #    elif w < h:
        #      cols = 3
        #      rows = 4
        #    elif w == h:
        #      cols = 4
        #      rows = 4
        #
        #    dx = w / cols
        #    dy = h / rows

        dx = min(w, h) / 5.0
        dy = dx

        # draw a solid color background
        cr.rectangle(x1, y1, w, h)
        cr.set_source_rgb(*pycha.color.hex2rgb("#eeeeff"))
        cr.fill()

        menus = self.m.get("menu", None)
        loadTl = self.m.get('loadTracklogs', None) # get the tracklog module
        tracklog = loadTl.getActiveTracklog()
        if tracklog.elevation == True:
            self.lineChart(cr, tracklog, 0, 0, w, h)

        # * draw "escape" button
        menus.drawButton(cr, x1, y1, dx, dy, "", "center:back;0.2;0.3>generic:;0.5;;0.5;;",
                         "set:menu:tracklogManager#tracklogInfo")

        if tracklog.trackpointsList == []:
            # there are no points to graph, so we quit
            return

        # * draw current elevation/position indicator
        pos = self.get('pos', None)
        if pos is not None:
            (pLat, pLon) = pos
            l = [geo.distance(pLat, pLon, i[2], i[3]) for i in tracklog.perElevList]
            totalLength = len(tracklog.perElevList)
            nearestIndex = l.index(min(l)) # get index of the shortest distance
            step = (w - 60 - 35) / totalLength # width minus padding divided by number of points

            currentPositionX = 60 + nearestIndex * step

            #      cr.set_source_rgba(1,1,1,1)
            #      cr.set_line_width(5)
            cr.move_to(currentPositionX, 0 + 30)
            cr.line_to(currentPositionX, h - 40)
            #      cr.stroke_preserve()
            cr.set_source_rgba(1.0, 0.5, 0, 1)
            cr.set_line_width(3)
            cr.stroke()
            cr.fill()

        #      nearestPoint = tracklog.perElevList[nearestIndex] # get the nearest point
        #      proj = self.m.get('projection', None)
        #      (nLat,nLon) = (nearestPoint[2],nearestPoint[3])
        return
Example #34
0
    def drawMenu(self, cr, menuName, args=None):
        # is this menu the correct menu ?
        if menuName != 'routeProfile':
            return  # we aren't the active menu so we don't do anything
        (x1, y1, w, h) = self.get('viewport', None)
        #    if w > h:
        #      cols = 4
        #      rows = 3
        #    elif w < h:
        #      cols = 3
        #      rows = 4
        #    elif w == h:
        #      cols = 4
        #      rows = 4
        #
        #    dx = w / cols
        #    dy = h / rows

        dx = min(w, h) / 5.0
        dy = dx

        # draw a solid color background
        cr.rectangle(x1, y1, w, h)
        cr.set_source_rgb(*pycha.color.hex2rgb("#eeeeff"))
        cr.fill()

        menus = self.m.get("menu", None)
        loadTl = self.m.get('loadTracklogs', None)  # get the tracklog module
        tracklog = loadTl.get_active_tracklog()
        if tracklog.elevation == True:
            self.lineChart(cr, tracklog, 0, 0, w, h)

        # * draw "escape" button
        menus.drawButton(cr, x1, y1, dx, dy, "",
                         "center:back;0.2;0.3>generic:;0.5;;0.5;;",
                         "set:menu:tracklogManager#tracklogInfo")

        if tracklog.trackpointsList == []:
            # there are no points to graph, so we quit
            return

        # * draw current elevation/position indicator
        pos = self.get('pos', None)
        if pos is not None:
            (pLat, pLon) = pos
            l = [
                geo.distance(pLat, pLon, i[2], i[3])
                for i in tracklog.perElevList
            ]
            totalLength = len(tracklog.perElevList)
            nearestIndex = l.index(
                min(l))  # get index of the shortest distance
            step = (
                w - 60 - 35
            ) / totalLength  # width minus padding divided by number of points

            currentPositionX = 60 + nearestIndex * step

            #      cr.set_source_rgba(1,1,1,1)
            #      cr.set_line_width(5)
            cr.move_to(currentPositionX, 0 + 30)
            cr.line_to(currentPositionX, h - 40)
            #      cr.stroke_preserve()
            cr.set_source_rgba(1.0, 0.5, 0, 1)
            cr.set_line_width(3)
            cr.stroke()
            cr.fill()

        #      nearestPoint = tracklog.perElevList[nearestIndex] # get the nearest point
        #      proj = self.m.get('projection', None)
        #      (nLat,nLon) = (nearestPoint[2],nearestPoint[3])
        return
Example #35
0
    def drawSimpleTrack(self, cr, GPXTracklog, colorName='navy'):
    #    pointsDrawn = 0
    #    start = clock()
        proj = self.m.get('projection', None)

        #are the projection and screen usable ?
        if proj is None or GPXTracklog is None:
            # we don't have WHAT to draw or HOW or BOTH :D
            self.log.info("skipping drawing of one track (tracklog or projection == None)")
            return

        (screenCentreX, screenCentreY, screenRadius) = proj.screenRadius()
        #    cr.set_source_rgb(0,0, 0.5)
        cr.set_source_color(gtk.gdk.color_parse(colorName))
        cr.set_line_width(self.lineWidth)
        numberOfClusters = len(GPXTracklog.clusters) # how many clusters do we have in this tracklog ?
        for cluster in GPXTracklog.clusters: # we draw all clusters in tracklog

            # do we see this cluster ?
            clusterCentreX = cluster.centreX
            clusterCentreY = cluster.centreY
            clusterRadius = cluster.radius
            screenToClusterDistance = geo.distance(screenCentreX, screenCentreY, clusterCentreX, clusterCentreY)
            if (screenToClusterDistance - (screenRadius + clusterRadius)) >= 0:
                continue # we don't see this cluster se we skip it
            clusterNr = GPXTracklog.clusters.index(cluster)
            #self.log.debug("Cluster nr %d" % clusterNr)
            # now we need to draw lines to connect neighboring clusters
            prevClusterNr = clusterNr - 1
            nextClusterNr = clusterNr + 1
            if prevClusterNr >= 0: # the 0th cluster has no previous cluster
                prevCluster = GPXTracklog.clusters[prevClusterNr]
                thisClusterLen = len(cluster.pointsList)
                prevClusterFirstPoint = prevCluster.pointsList[0]
                thisClusterLastPoint = cluster.pointsList[thisClusterLen - 1]
                (x1, y1) = proj.ll2xy(thisClusterLastPoint['latitude'], thisClusterLastPoint['longitude'])
                (x2, y2) = proj.ll2xy(prevClusterFirstPoint['latitude'], prevClusterFirstPoint['longitude'])
                self.drawLineSegment(cr, x1, y1, x2, y2) # now we connect the two clusters
                #self.point(cr, x1, y1)
                #self.point(cr, x2, y2)

            if nextClusterNr <= (numberOfClusters - 1): # the last cluster has no next cluster
                nextCluster = GPXTracklog.clusters[nextClusterNr]
                nextClusterLen = len(nextCluster.pointsList)
                nextClusterLastPoint = nextCluster.pointsList[nextClusterLen - 1]
                thisClusterFirstPoint = cluster.pointsList[0]
                (x1, y1) = proj.ll2xy(thisClusterFirstPoint['latitude'], thisClusterFirstPoint['longitude'])
                (x2, y2) = proj.ll2xy(nextClusterLastPoint['latitude'], nextClusterLastPoint['longitude'])
                self.drawLineSegment(cr, x1, y1, x2, y2) # now we connect the two clusters
                #self.point(cr, x1, y1)
                #self.point(cr, x2, y2)

                # get a list of onscreen coordinates
            #      points = map(lambda x: proj.ll2xy(x['latitude'], x['longitude']), cluster.pointsList)
            points = [proj.ll2xy(x['latitude'], x['longitude']) for x in cluster.pointsList]
            # draw these coordinates
            (x, y) = points[0]
            cr.move_to(x, y) # go the the first point
            [cr.line_to(x[0], x[1]) for x in points[1:]] # extend the line over the other points in the cluster

        cr.stroke()
        cr.fill()
Example #36
0
    def _doNavigationUpdate(self):
        """do a navigation update"""
        # make sure there really are some steps
        if not self.route:
            self.log.error("no route")
            return
        pos = self.get('pos', None)
        if pos is None:
            self.log.error("skipping update, invalid position")
            return

        # get/compute/update necessary the values
        (lat1, lon1) = pos
        currentStep = self.getCurrentStep()
        lat2, lon2 = currentStep.getLL()
        currentDistance = geo.distance(lat1, lon1, lat2, lon2) * 1000 # km to m
        self.currentDistance = currentDistance # update current distance

        # use some sane minimum distance
        distance = int(self.get('minAnnounceDistance', 100))

        # GHK: make distance speed-sensitive
        #
        # I came up with this formula after a lot of experimentation with
        # gnuplot.  The idea is to give the user some simple parameters to
        # adjust yet let him have a lot of control.  There are five
        # parameters in the equation:
        #
        # lowSpeed	Speed below which the pre-announcement time is constant.
        # lowTime	Announcement time at and below lowSpeed.
        # highSpeed	Speed above which the announcement time is constant.
        # highTime	Announcement time at and above highSpeed.
        # power	Exponential power used in the formula; good values are 0.5-5
        #
        # The speeds are in m/s.  Obviously highXXX must be greater than lowXXX.
        # If power is 1.0, announcement times increase linearly above lowSpeed.
        # If power < 1.0, times rise rapidly just above lowSpeed and more
        # gradually approaching highSpeed.  If power > 1.0, times rise
        # gradually at first and rapidly near highSpeed.  I like power > 1.0.
        #
        # The reasoning is that at high speeds you are probably on a
        # motorway/freeway and will need extra time to get into the proper
        # lane to take your exit.  That reasoning is pretty harmless on a
        # high-speed two-lane road, but it breaks down if you are stuck in
        # heavy traffic on a four-lane freeway (like in Los Angeles
        # where I live) because you might need quite a while to work your
        # way across the traffic even though you're creeping along.  But I
        # don't know a good way to detect whether you're on a multi-lane road,
        # I chose speed as an acceptable proxy.
        #
        # Regardless of speed, we always warn a certain distance ahead (see
        # "distance" above).  That distance comes from the value in the current
        # step of the directions.
        #
        # BTW, if you want to use gnuplot to play with the curves, try:
        # max(a,b) = a > b ? a : b
        # min(a,b) = a < b ? a : b
        # warn(x,t1,s1,t2,s2,p) = min(t2,(max(s1,x)-s1)**p*(t2-t1)/(s2-s1)**p+t1)
        # plot [0:160][0:] warn(x,10,50,60,100,2.0)
        #
        metersPerSecSpeed = self.get('metersPerSecSpeed', None)
        pointReachedDistance = int(self.get('pointReachedDistance', 30))

        if metersPerSecSpeed:
            # check if we can miss the point by going too fast -> mps speed > point reached distance
            # also enlarge the rerouting threshold as it looks like it needs to be larger
            # when moving at high speed to prevent unnecessary rerouting
            if metersPerSecSpeed > pointReachedDistance * 0.75:
                pointReachedDistance = metersPerSecSpeed * 2
            #        self.log.debug("tbt: enlarging point reached distance to: %1.2f m due to large speed (%1.2f m/s)". (pointReachedDistance, metersPerSecSpeed)

            if metersPerSecSpeed > INCREASE_REROUTING_THRESHOLD_SPEED:
                self.reroutingThresholdMultiplier = REROUTING_THRESHOLD_MULTIPLIER
            else:
                self.reroutingThresholdMultiplier = 1.0

            # speed & time based triggering
            lowSpeed = float(self.get('minAnnounceSpeed', 13.89))
            highSpeed = float(self.get('maxAnnounceSpeed', 27.78))
            highSpeed = max(highSpeed, lowSpeed + 0.1)
            lowTime = int(self.get('minAnnounceTime', 10))
            highTime = int(self.get('maxAnnounceTime', 60))
            highTime = max(highTime, lowTime)
            power = float(self.get('announcePower', 2.0))
            warnTime = (max(lowSpeed, metersPerSecSpeed) - lowSpeed) ** power \
                       * (highTime - lowTime) / (highSpeed - lowSpeed) ** power \
                       + lowTime
            warnTime = min(highTime, warnTime)
            distance = max(distance, warnTime * metersPerSecSpeed)

            if self.get('debugTbT', False):
                self.log.debug("#####")
                self.log.debug("min/max announce time: %d/%d s", lowTime, highTime)
                self.log.debug("trigger distance: %1.2f m (%1.2f s warning)", distance, distance / float(metersPerSecSpeed))
                self.log.debug("current distance: %1.2f m", currentDistance)
                self.log.debug("current speed: %1.2f m/s (%1.2f km/h)", metersPerSecSpeed, metersPerSecSpeed * 3.6)
                self.log.debug("point reached distance: %f m", pointReachedDistance)
                self.log.debug("1. triggered=%r, 1.5. triggered=%r, 2. triggered=%r",
                               self.espeakFirstTrigger, self.espeakFirstAndHalfTrigger, self.espeakSecondTrigger)
                if warnTime > 30:
                    self.log.debug("optional (20 s) trigger distance: %1.2f", 20.0 * metersPerSecSpeed)

            if currentDistance <= pointReachedDistance:
                # this means we reached the point"""
                if self.espeakSecondTrigger == False:
                    self.log.debug("triggering espeak nr. 2")
                    # say the message without distance
                    plaintextMessage = currentStep.ssmlMessage
                    # consider turn said even if it was skipped (ignore errors)
                    self.sayTurn(plaintextMessage, 0)
                    self.markCurrentStepAsVisited() # mark this point as visited
                    self.espeakFirstTrigger = True # everything has been said, again :D
                    self.espeakSecondTrigger = True # everything has been said, again :D
                self.switchToNextStep() # switch to next step
            else:
                if currentDistance <= distance:
                    # this means we reached an optimal distance for saying the message"""
                    if self.espeakFirstTrigger == False:
                        self.log.debug("triggering espeak nr. 1")
                        plaintextMessage = currentStep.ssmlMessage
                        if self.sayTurn(plaintextMessage, currentDistance):
                            self.espeakFirstTrigger = True # first message done
                if self.espeakFirstAndHalfTrigger == False and warnTime > 30:
                    if currentDistance <= (20.0 * metersPerSecSpeed):
                        # in case that the warning time gets too big, add an intermediate warning at 20 seconds
                        # NOTE: this means it is said after the first trigger
                        plaintextMessage = currentStep.ssmlMessage
                        if self.sayTurn(plaintextMessage, currentDistance):
                            self.espeakFirstAndHalfTrigger = True # intermediate message done

            ## automatic rerouting ##

            # is automatic rerouting enabled from options
            # enabled == threshold that is not not None
            if self._automaticReroutingEnabled():
                # rerouting is enabled only once the route is reached for the first time
                if self.onRoute and not self.routeReached:
                    self.routeReached = True
                    self.automaticRerouteCounter = 0
                    self.log.info('route reached, rerouting enabled')

                # did the TBT worker detect that the rerouting threshold was reached ?
                if self._reroutingConditionsMet():
                    # test if enough consecutive divergence point were recorded
                    if self.reroutingThresholdCrossedCounter >= REROUTING_TRIGGER_COUNT:
                        # reset the routeReached override
                        self.overrideRouteReached = False
                        # trigger rerouting
                        self._rerouteAuto()
                else:
                    # reset the counter
                    self.reroutingThresholdCrossedCounter = 0
Example #37
0
    def start_tbt(self, from_where='first'):
        """Start Turn-by-turn navigation."""

        # clean up any possible previous navigation data
        self._go_to_initial_state()

        # NOTE: turn and step are used interchangeably in the documentation
        m = self.m.get('route', None)
        if m:
            route = m.get_current_directions()
            if route:  # is the route nonempty ?
                self._route = route
                # get route in radians for automatic rerouting
                self.radiansRoute = route.get_points_lle_radians(
                    drop_elevation=True)
                # start rerouting watch
                self._start_tbt_worker()

                # for some reason the combined distance does not account for the last step
                self._m_route_length = route.length

                # some statistics
                meters_per_sec_speed = self.get('metersPerSecSpeed', None)
                dt = m.route_lookup_duration
                self.log.info("route lookup took: %f s" % dt)
                if dt and meters_per_sec_speed:
                    dm = dt * meters_per_sec_speed
                    self.log.info("distance traveled during lookup: %f m" % dm)
                    # the duration of the road lookup and other variables are currently not used
                # in the heuristics but might be added later to make the heuristics more robust

                # now we decide if we use the closest turn, or the next one,
                # as we might be already past it and on our way to the next turn
                cs = self._get_closest_step(
                )  # get geographically closest step
                pos = self.get('pos', None)  # get current position
                p_reached_dist = int(self.get('pointReachedDistance',
                                              30))  # get the trigger distance
                next_turn_id = self._get_step_index(cs) + 1
                next_step = self._get_step(next_turn_id)
                # check if we have all the data needed for our heuristics
                self.log.info(
                    "trying to guess correct step to start navigation")
                if next_step and pos and p_reached_dist:
                    (lat, lon) = pos
                    (csLat, csLon) = cs.getLL()
                    (nsLat, nsLon) = next_step.getLL()
                    pos2next_step = geo.distance(lat, lon, nsLat, nsLon) * 1000
                    pos2current_step = geo.distance(lat, lon, csLat,
                                                    csLon) * 1000
                    current_step2next_step = geo.distance(
                        csLat, csLon, nsLat, nsLon) * 1000
                    #          self.log.debug("pos",(lat,lon))
                    #          self.log.debug("cs",(csLat,csLon))
                    #          self.log.debug("ns",(nsLat,nsLon))
                    self.log.debug("position to next turn: %f m" %
                                   pos2next_step)
                    self.log.debug("position to current turn: %f m" %
                                   pos2current_step)
                    self.log.debug("current turn to next turn: %f m" %
                                   current_step2next_step)
                    self.log.debug("turn reached trigger distance: %f m" %
                                   p_reached_dist)

                    if pos2current_step > p_reached_dist:
                        # this means we are out of the "capture circle" of the closest step

                        # what is more distant, the closest or the next step ?
                        if pos2next_step < current_step2next_step:
                            # we are mostly probably already past the closest step,
                            # so we switch to the next step at once
                            self.log.debug(
                                "already past closest turn, switching to next turn"
                            )
                            self.current_step = next_step
                            # we play the message for the next step,
                            # with current distance to this step,
                            # to assure there is some voice output immediately after
                            # getting a new route or rerouting"""
                            plaintextMessage = next_step.ssml_message
                            self._say_turn(plaintextMessage, pos2next_step)
                        else:
                            # we have probably not yet reached the closest step,
                            # so we start navigation from it
                            self.log.debug("closest turn not yet reached")
                            self.current_step = cs

                    else:
                        # we are inside the  "capture circle" of the closest step,
                        # this means the navigation will trigger the voice message by itself
                        # and correctly switch to next step
                        # -> no need to switch to next step from here
                        self.log.debug("inside reach distance of closest turn")
                        self.current_step = cs

                else:
                    # we dont have some of the data, that is needed to decide
                    # if we start the navigation from the closest step of from the step that is after it
                    # -> we just start from the closest step
                    self.log.debug(
                        "not enough data to decide, using closest turn")
                    self.current_step = cs
        self._do_navigation_update()  # run a first time navigation update
        self._location_watch_id = self.watch('locationUpdated',
                                             self.location_update_cb)
        self.log.info("started and ready")
        # trigger the navigation-started signal
        self.navigation_started()
    def drawSimpleTrack(self, cr, GPXTracklog, colorName='navy'):
        #    pointsDrawn = 0
        #    start = clock()
        proj = self.m.get('projection', None)

        #are the projection and screen usable ?
        if proj is None or GPXTracklog is None:
            # we don't have WHAT to draw or HOW or BOTH :D
            self.log.info(
                "skipping drawing of one track (tracklog or projection == None)"
            )
            return

        (screenCentreX, screenCentreY, screenRadius) = proj.screenRadius()
        #    cr.set_source_rgb(0,0, 0.5)
        cr.set_source_color(gtk.gdk.color_parse(colorName))
        cr.set_line_width(self.lineWidth)
        numberOfClusters = len(
            GPXTracklog.clusters
        )  # how many clusters do we have in this tracklog ?
        for cluster in GPXTracklog.clusters:  # we draw all clusters in tracklog

            # do we see this cluster ?
            clusterCentreX = cluster.centreX
            clusterCentreY = cluster.centreY
            clusterRadius = cluster.radius
            screenToClusterDistance = geo.distance(screenCentreX,
                                                   screenCentreY,
                                                   clusterCentreX,
                                                   clusterCentreY)
            if (screenToClusterDistance - (screenRadius + clusterRadius)) >= 0:
                continue  # we don't see this cluster se we skip it
            clusterNr = GPXTracklog.clusters.index(cluster)
            #self.log.debug("Cluster nr %d" % clusterNr)
            # now we need to draw lines to connect neighboring clusters
            prevClusterNr = clusterNr - 1
            nextClusterNr = clusterNr + 1
            if prevClusterNr >= 0:  # the 0th cluster has no previous cluster
                prevCluster = GPXTracklog.clusters[prevClusterNr]
                thisClusterLen = len(cluster.pointsList)
                prevClusterFirstPoint = prevCluster.pointsList[0]
                thisClusterLastPoint = cluster.pointsList[thisClusterLen - 1]
                (x1, y1) = proj.ll2xy(thisClusterLastPoint['latitude'],
                                      thisClusterLastPoint['longitude'])
                (x2, y2) = proj.ll2xy(prevClusterFirstPoint['latitude'],
                                      prevClusterFirstPoint['longitude'])
                self.drawLineSegment(cr, x1, y1, x2,
                                     y2)  # now we connect the two clusters
                #self.point(cr, x1, y1)
                #self.point(cr, x2, y2)

            if nextClusterNr <= (numberOfClusters -
                                 1):  # the last cluster has no next cluster
                nextCluster = GPXTracklog.clusters[nextClusterNr]
                nextClusterLen = len(nextCluster.pointsList)
                nextClusterLastPoint = nextCluster.pointsList[nextClusterLen -
                                                              1]
                thisClusterFirstPoint = cluster.pointsList[0]
                (x1, y1) = proj.ll2xy(thisClusterFirstPoint['latitude'],
                                      thisClusterFirstPoint['longitude'])
                (x2, y2) = proj.ll2xy(nextClusterLastPoint['latitude'],
                                      nextClusterLastPoint['longitude'])
                self.drawLineSegment(cr, x1, y1, x2,
                                     y2)  # now we connect the two clusters
                #self.point(cr, x1, y1)
                #self.point(cr, x2, y2)

                # get a list of onscreen coordinates
            #      points = map(lambda x: proj.ll2xy(x['latitude'], x['longitude']), cluster.pointsList)
            points = [
                proj.ll2xy(x['latitude'], x['longitude'])
                for x in cluster.pointsList
            ]
            # draw these coordinates
            (x, y) = points[0]
            cr.move_to(x, y)  # go the the first point
            [cr.line_to(x[0], x[1]) for x in points[1:]
             ]  # extend the line over the other points in the cluster

        cr.stroke()
        cr.fill()
Example #39
0
    def _do_navigation_update(self):
        """Do a navigation update."""
        # make sure there really are some steps
        if not self._route:
            self.log.error("no route")
            return
        pos = self.get('pos', None)
        if pos is None:
            self.log.error("skipping update, invalid position")
            return

        # get/compute/update necessary the values
        lat1, lon1 = pos
        lat2, lon2 = self.current_step.getLL()
        current_distance = geo.distance(lat1, lon1, lat2,
                                        lon2) * 1000  # km to m
        self._current_distance = current_distance  # update current distance

        # use some sane minimum distance
        distance = int(self.get('minAnnounceDistance', 100))

        # GHK: make distance speed-sensitive
        #
        # I came up with this formula after a lot of experimentation with
        # gnuplot.  The idea is to give the user some simple parameters to
        # adjust yet let him have a lot of control.  There are five
        # parameters in the equation:
        #
        # low_speed	Speed below which the pre-announcement time is constant.
        # low_time	Announcement time at and below lowSpeed.
        # high_speed	Speed above which the announcement time is constant.
        # high_time	Announcement time at and above highSpeed.
        # power	Exponential power used in the formula; good values are 0.5-5
        #
        # The speeds are in m/s.  Obviously highXXX must be greater than lowXXX.
        # If power is 1.0, announcement times increase linearly above lowSpeed.
        # If power < 1.0, times rise rapidly just above lowSpeed and more
        # gradually approaching highSpeed.  If power > 1.0, times rise
        # gradually at first and rapidly near highSpeed.  I like power > 1.0.
        #
        # The reasoning is that at high speeds you are probably on a
        # motorway/freeway and will need extra time to get into the proper
        # lane to take your exit.  That reasoning is pretty harmless on a
        # high-speed two-lane road, but it breaks down if you are stuck in
        # heavy traffic on a four-lane freeway (like in Los Angeles
        # where I live) because you might need quite a while to work your
        # way across the traffic even though you're creeping along.  But I
        # don't know a good way to detect whether you're on a multi-lane road,
        # I chose speed as an acceptable proxy.
        #
        # Regardless of speed, we always warn a certain distance ahead (see
        # "distance" above).  That distance comes from the value in the current
        # step of the directions.
        #
        # BTW, if you want to use gnuplot to play with the curves, try:
        # max(a,b) = a > b ? a : b
        # min(a,b) = a < b ? a : b
        # warn(x,t1,s1,t2,s2,p) = min(t2,(max(s1,x)-s1)**p*(t2-t1)/(s2-s1)**p+t1)
        # plot [0:160][0:] warn(x,10,50,60,100,2.0)
        #
        meters_per_sec_speed = self.get('metersPerSecSpeed', None)
        point_reached_distance = int(self.get('pointReachedDistance', 30))

        if meters_per_sec_speed:
            # check if we can miss the point by going too fast -> mps speed > point reached distance
            # also enlarge the rerouting threshold as it looks like it needs to be larger
            # when moving at high speed to prevent unnecessary rerouting
            if meters_per_sec_speed > point_reached_distance * 0.75:
                point_reached_distance = meters_per_sec_speed * 2
            #        self.log.debug("tbt: enlarging point reached distance to: %1.2f m due to large speed (%1.2f m/s)". (pointReachedDistance, metersPerSecSpeed)

            if meters_per_sec_speed > INCREASE_REROUTING_THRESHOLD_SPEED:
                self._rerouting_threshold_multiplier = REROUTING_THRESHOLD_MULTIPLIER
            else:
                self._rerouting_threshold_multiplier = 1.0

            # speed & time based triggering
            low_speed = float(self.get('minAnnounceSpeed', 13.89))
            high_speed = float(self.get('maxAnnounceSpeed', 27.78))
            high_speed = max(high_speed, low_speed + 0.1)
            low_time = int(self.get('minAnnounceTime', 10))
            high_time = int(self.get('maxAnnounceTime', 60))
            high_time = max(high_time, low_time)
            power = float(self.get('announcePower', 2.0))
            warn_time = (max(low_speed, meters_per_sec_speed) - low_speed) ** power \
                       * (high_time - low_time) / (high_speed - low_speed) ** power \
                       + low_time
            warn_time = min(high_time, warn_time)
            distance = max(distance, warn_time * meters_per_sec_speed)

            if self.get('debugTbT', False):
                self.log.debug("#####")
                self.log.debug("min/max announce time: %d/%d s", low_time,
                               high_time)
                self.log.debug("trigger distance: %1.2f m (%1.2f s warning)",
                               distance,
                               distance / float(meters_per_sec_speed))
                self.log.debug("current distance: %1.2f m", current_distance)
                self.log.debug("current speed: %1.2f m/s (%1.2f km/h)",
                               meters_per_sec_speed,
                               meters_per_sec_speed * 3.6)
                self.log.debug("point reached distance: %f m",
                               point_reached_distance)
                self.log.debug(
                    "1. triggered=%r, 1.5. triggered=%r, 2. triggered=%r",
                    self._espeak_first_trigger,
                    self._espeak_first_and_half_trigger,
                    self._espeak_second_trigger)
                if warn_time > 30:
                    self.log.debug("optional (20 s) trigger distance: %1.2f",
                                   20.0 * meters_per_sec_speed)

            if current_distance <= point_reached_distance:
                # this means we reached the point"""
                if self._espeak_second_trigger is False:
                    self.log.debug("triggering espeak nr. 2")
                    # say the message without distance
                    plaintextMessage = self.current_step.ssml_message
                    # consider turn said even if it was skipped (ignore errors)
                    self._say_turn(plaintextMessage, 0)
                    self.current_step.visited = True  # mark this point as visited
                    self._espeak_first_trigger = True  # everything has been said, again :D
                    self._espeak_second_trigger = True  # everything has been said, again :D
                self.switch_to_next_step()  # switch to next step
            else:
                if current_distance <= distance:
                    # this means we reached an optimal distance for saying the message"""
                    if self._espeak_first_trigger is False:
                        self.log.debug("triggering espeak nr. 1")
                        plaintextMessage = self.current_step.ssml_message
                        if self._say_turn(plaintextMessage, current_distance):
                            self._espeak_first_trigger = True  # first message done
                if self._espeak_first_and_half_trigger is False and warn_time > 30:
                    if current_distance <= (20.0 * meters_per_sec_speed):
                        # in case that the warning time gets too big, add an intermediate warning at 20 seconds
                        # NOTE: this means it is said after the first trigger
                        plaintextMessage = self.current_step.ssml_message
                        if self._say_turn(plaintextMessage, current_distance):
                            self._espeak_first_and_half_trigger = True  # intermediate message done

            ## automatic rerouting ##

            # is automatic rerouting enabled from options
            # enabled == threshold that is not not None
            if self.automatic_rerouting_enabled:
                # rerouting is enabled only once the route is reached for the first time
                if self._on_route and not self._route_reached:
                    self._route_reached = True
                    self._automatic_reroute_counter = 0
                    self.log.info('route reached, rerouting enabled')

                # did the TBT worker detect that the rerouting threshold was reached ?
                if self._rerouting_conditions_met():
                    # test if enough consecutive divergence point were recorded
                    if self._rerouting_threshold_crossed_counter >= REROUTING_TRIGGER_COUNT:
                        # reset the routeReached override
                        self._override_route_reached = False
                        # trigger rerouting
                        self._reroute_auto()
                else:
                    # reset the counter
                    self._rerouting_threshold_crossed_counter = 0