diff --git a/scripts/gdb_printers.py b/scripts/gdb_printers.py index fd85ff358..277f2ba79 100644 --- a/scripts/gdb_printers.py +++ b/scripts/gdb_printers.py @@ -4,6 +4,19 @@ import gdb.printing # https://sourceware.org/gdb/onlinedocs/gdb/Writing-a-Pretty_002dPrinter.html COORDINATE_PRECISION = 1e6 +coord2float = lambda x: int(x) / COORDINATE_PRECISION +lonlat = lambda x: (coord2float(x['lon']['__value']), coord2float(x['lat']['__value'])) + +def call(this, method, *args): + """Call this.method(args)""" + command = '(*({})({})).{}({})'.format(this.type.target().pointer(), this.address, method, ','.join((str(x) for x in args))) + return gdb.parse_and_eval(command) + +def iterate(v): + s, e = v['_M_impl']['_M_start'], v['_M_impl']['_M_finish'] + while s != e: + yield s.dereference() + s +=1 class CoordinatePrinter: """Print a CoordinatePrinter object.""" @@ -11,8 +24,7 @@ class CoordinatePrinter: self.val = val def to_string(self): - lon, lat = int(self.val['lon']['__value']), int(self.val['lat']['__value']) - return '{{{}, {}}}'.format(float(lon) / COORDINATE_PRECISION, float(lat) / COORDINATE_PRECISION) + return '{{{}, {}}}'.format(*lonlat(self.val)) class TurnInstructionPrinter: """Print a TurnInstruction object.""" @@ -59,5 +71,263 @@ def build_pretty_printer(): pp.add_printer('TurnLaneData', '::TurnLaneData$', TurnLaneDataPrinter) return pp -#gdb.pretty_printers = [filter(lambda x: x.name != 'OSRM', gdb.pretty_printers)] +gdb.pretty_printers = [x for x in gdb.pretty_printers if x.name != 'OSRM'] # unregister OSRM pretty printer before (re)loading gdb.printing.register_pretty_printer(gdb.current_objfile(), build_pretty_printer()) + + +import geojson +import os +import time +import tempfile +import urllib.parse +import webbrowser +import re + +class GeojsonPrinter (gdb.Command): + """Display features on geojson.io.""" + + def __init__ (self): + super (GeojsonPrinter, self).__init__ ('geojson', gdb.COMMAND_USER) + self.to_geojson = { + 'osrm::engine::guidance::RouteSteps': self.RouteSteps, + 'std::vector >': self.RouteSteps} + + @staticmethod + def encodeURIComponent(s): + return urllib.parse.quote(s.encode('utf-8'), safe='~()*!.\'') + + @staticmethod + def RouteSteps(steps): + k, road, result = 0, [], [] + for step in iterate(steps): + maneuver, location = step['maneuver'], step['maneuver']['location'] + ll = lonlat(location) + road.append(ll) + + properties= {field.name: str(step[field.name]) for field in step.type.fields() if str(step[field.name]) != '""'} + properties.update({'maneuver.' + field.name: str(maneuver[field.name]) for field in maneuver.type.fields()}) + properties.update({'stroke': '#0000ff', 'stroke-opacity': 0.8, 'stroke-width': 15}) + result.append(geojson.Feature(geometry=geojson.LineString([ll, ll]), properties=properties)) + + road = geojson.Feature(geometry=geojson.LineString(road), properties={'stroke': '#0000ff', 'stroke-opacity': 0.5, 'stroke-width':5}) + return [road, *result] + + def invoke (self, arg, from_tty): + try: + val = gdb.parse_and_eval(arg) + features = self.to_geojson[str(val.type)](val) + request = self.encodeURIComponent(str(geojson.FeatureCollection(features))) + webbrowser.open('http://geojson.io/#data=data:application/json,' + request) + except KeyError as e: + print ('no GeoJSON printer for: ' + str(e)) + except gdb.error as e: + print('error: ' % (e.args[0] if len(e.args)>0 else 'unspecified')) + return + +GeojsonPrinter() + + + +class SVGPrinter (gdb.Command): + """ + Generate SVG representation within HTML of edge-based graph in facade. + SVG image contains: + - thick lines with arrow heads are edge-based graph nodes with forward (green) and reverse (red) node IDs (large font) + - segments weights are numbers (small font) in the middle of segments in forward (green) or reverse (red) direction + - thin lines are edge-based graph edges in forward (green), backward (red) or both (yellow) directions with + weights, edge-based graph node IDs (source, targte) and some algorithm-specific information + - coordinates of segments end points (node-based graph nodes) + """ + + def __init__ (self): + super (SVGPrinter, self).__init__ ('svg', gdb.COMMAND_USER) + self.re_bbox = None + self.to_svg = { + 'const osrm::engine::datafacade::ContiguousInternalMemoryDataFacade &': self.Facade, + 'const osrm::engine::datafacade::ContiguousInternalMemoryDataFacade &': self.Facade, + 'const osrm::engine::datafacade::ContiguousInternalMemoryDataFacade &': self.Facade} + + + @staticmethod + def show_svg(svg, width, height): + svg = """ + + + + + + + + + + + + + + + + + + +""" + svg + '\n' + fd, name = tempfile.mkstemp('.html') + os.write(fd, svg.encode('utf-8')) + os.close(fd) + print ('Saved to ' + name) + webbrowser.open('file://' + name) + + @staticmethod + def getByGeometryId(facade, id, value): + return call(facade, 'GetUncompressed' + ('Forward' if id['forward'] else 'Reverse') + value, id['id']) + + @staticmethod + def getNodesInBoundingBox(facade, bbox): + nodes, longitudes, latitudes = set(), set(), set() + for node in range(call(facade, 'GetNumberOfNodes')): + geometry = SVGPrinter.getByGeometryId(facade, call(facade, 'GetGeometryIndex', node), 'Geometry') + node_longitudes, node_latitudes, in_bbox = set(), set(), False + for nbg_node in iterate(geometry): + lon, lat = lonlat(call(facade, 'GetCoordinateOfNode', nbg_node)) + node_longitudes.add(lon) + node_latitudes.add(lat) + in_bbox = in_bbox or bbox[0] <= lon and lon <= bbox[2] and bbox[1] <= lat and lat <= bbox[3] + if in_bbox: + nodes.add(node) + longitudes.update(node_longitudes) + latitudes.update(node_latitudes) + return nodes, longitudes, latitudes + + @staticmethod + def Facade(facade, width, height, bbox): + marginx, marginy = 75, 75 + INVALID_SEGMENT_WEIGHT, MAX_SEGMENT_WEIGHT = gdb.parse_and_eval('INVALID_SEGMENT_WEIGHT'), gdb.parse_and_eval('INVALID_SEGMENT_WEIGHT') + segment_weight = lambda x: str(x) + (' invalid' if x == INVALID_SEGMENT_WEIGHT else ' max' if x == MAX_SEGMENT_WEIGHT else '') + + ## get nodes + nodes, longitudes, latitudes = SVGPrinter.getNodesInBoundingBox(facade, bbox) + if len(nodes) == 0: + return '' + + ## create transformations (lon,lat) -> (x,y) + minx, miny, maxx, maxy = min(longitudes), min(latitudes), max(longitudes), max(latitudes) + if abs(maxx - minx) < 1e-8: + maxx += (maxy - miny) / 2 + minx -= (maxy - miny) / 2 + if abs(maxy - miny) < 1e-8: + maxy += (maxx - minx) / 2 + miny -= (maxx - minx) / 2 + tx = lambda x: marginx + (x - minx) * (width - 2 * marginx) / (maxx - minx) + ty = lambda y: marginy + (maxy - y) * (height - 2 * marginy) / (maxy - miny) + t = lambda x: str(tx(x[0])) + ',' + str(ty(x[1])) + + print ('Graph has {} nodes and {} edges and {} nodes in the input bounding box {},{};{},{} -> {},{};{},{}' + .format(call(facade, 'GetNumberOfNodes'), call(facade, 'GetNumberOfEdges'), len(nodes), *bbox, minx, miny, maxx, maxy)) + + result = '' + for node in nodes: + geometry_id = call(facade, 'GetGeometryIndex', node) + direction = 'forward' if geometry_id['forward'] else 'reverse' + geometry = SVGPrinter.getByGeometryId(facade, geometry_id, 'Geometry') + weights = SVGPrinter.getByGeometryId(facade, geometry_id, 'Weights') + + ## add the edge-based node + ref = 'n' + str(node) + result += '' + result += '' + str(node) + '\n' + + ## add segments with weights + geometry_first = geometry['_M_impl']['_M_start'] + for segment, weight in enumerate(iterate(weights)): + ref = 's' + str(node) + '.' + str(segment) + result += ''\ + + ''\ + + '' \ + + segment_weight(weight) + '\n' + geometry_first += 1 + + ## add edge-based edges + s0, s1 = geometry['_M_impl']['_M_start'].dereference(), (geometry['_M_impl']['_M_start'] + 1).dereference() + for edge in range(call(facade, 'BeginEdges', node), call(facade, 'EndEdges', node)): + target, edge_data = call(facade, 'GetTarget', edge), call(facade, 'GetEdgeData', edge) + direction = 'both' if edge_data['forward'] and edge_data['backward'] else 'forward' if edge_data['forward'] else 'backward' + target_geometry = SVGPrinter.getByGeometryId(facade, call(facade, 'GetGeometryIndex', target), 'Geometry') + t0, t1 = target_geometry['_M_impl']['_M_start'].dereference(), (target_geometry['_M_impl']['_M_start'] + 1).dereference() + + ## the source point: the first node of the source node's first segment + s0x, s0y = lonlat(call(facade, 'GetCoordinateOfNode', s0)) + + ## the first control point: the node orthogonal to the first segment at the middle of the segment and offset distance length / 4 + s1x, s1y = lonlat(call(facade, 'GetCoordinateOfNode', s1)) + d0x, d0y = s1x - s0x, s1y - s0y + c0x, c0y = s0x + d0x /2 - d0y /4, s0y + d0y / 2 + d0x /4 + + ## the end point: middle of the first segment of the target node + t0x, t0y = lonlat(call(facade, 'GetCoordinateOfNode', t0)) + t1x, t1y = lonlat(call(facade, 'GetCoordinateOfNode', t1)) + d1x, d1y = t1x - t0x, t1y - t0y + e1x, e1y = t0x + d1x / 2, t0y + d1y / 2 + + ## the second control point: the first node of the target's node first segment + c1x, c1y = t0x, t0y + + ref = 'e' + str(edge) + edge_arrow = ('↔' if edge_data['backward'] else '→') if edge_data['forward'] else ('←' if edge_data['backward'] else '?') + text = str(node) + edge_arrow + str(target) + ' ' + str(edge_data['weight']) \ + + (', shortcut' if 'shortcut' in set([x.name for x in edge_data.type.target().fields()]) and edge_data['shortcut'] else '') + result += ''\ + + ''\ + + '' \ + + text + '\n' + result += '\n\n' + + ## add longitudes and latitudes + for lon in longitudes: + result += '' + str(lon) + '\n' + for lat in latitudes: + result += '' + str(lat) + '\n' + return result + + def invoke (self, arg, from_tty): + try: + argv = arg.split(' ') + if len(argv) == 0 or len(argv[0]) == 0: + print ('no argument specified\nsvg [BOUNDING BOX west,south;east,north] [SIZE width,height]') + return + val = gdb.parse_and_eval(argv[0]) + dims = re.search('([0-9]+)x([0-9]+)', arg) + width, height = [int(x) for x in dims.groups()] if dims else (2100, 1600) + re_float = '[-+]?[0-9]*\.?[0-9]+' + bbox = re.search('(' + re_float + '),(' + re_float + ');(' + re_float + '),(' + re_float +')', arg) + bbox = [float(x) for x in bbox.groups()] if bbox else [-180, -90, 180, 90] + svg = self.to_svg[str(val.type)](val, width, height, bbox) + self.show_svg(svg, width, height) + except KeyError as e: + print ('no SVG printer for: ' + str(e)) + except gdb.error as e: + print('error: ' % (e.args[0] if len(e.args)>0 else 'unspecified')) + +SVGPrinter()