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
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
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
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
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
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
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
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()
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)
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)
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)
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
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
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()
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
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)
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
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
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
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")
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
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)
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
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")
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")
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")
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()
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()
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()
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
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
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()
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
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()
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