1#!/usr/local/bin/python3.8 2# Eclipse SUMO, Simulation of Urban MObility; see https://eclipse.org/sumo 3# Copyright (C) 2012-2019 German Aerospace Center (DLR) and others. 4# This program and the accompanying materials 5# are made available under the terms of the Eclipse Public License v2.0 6# which accompanies this distribution, and is available at 7# http://www.eclipse.org/legal/epl-v20.html 8# SPDX-License-Identifier: EPL-2.0 9 10# @file route2poly.py 11# @author Jakob Erdmann 12# @author Michael Behrisch 13# @date 2012-11-15 14# @version $Id$ 15 16""" 17From a sumo network and a route file, this script generates a polygon (polyline) for every route 18which can be loaded with sumo-gui for visualization 19""" 20from __future__ import absolute_import 21import sys 22import os 23import itertools 24import random 25from collections import defaultdict 26from optparse import OptionParser 27sys.path.append(os.path.join(os.environ['SUMO_HOME'], 'tools')) 28from sumolib.output import parse # noqa 29from sumolib.net import readNet # noqa 30from sumolib.miscutils import Colorgen # noqa 31from sumolib import geomhelper # noqa 32 33 34def parse_args(args): 35 USAGE = "Usage: " + sys.argv[0] + " <netfile> <routefile> [options]" 36 optParser = OptionParser() 37 optParser.add_option("-o", "--outfile", help="name of output file") 38 optParser.add_option("-u", "--hue", default="random", 39 help="hue for polygons (float from [0,1] or 'random')") 40 optParser.add_option("-s", "--saturation", default=1, 41 help="saturation for polygons (float from [0,1] or 'random')") 42 optParser.add_option("-b", "--brightness", default=1, 43 help="brightness for polygons (float from [0,1] or 'random')") 44 optParser.add_option("-l", "--layer", default=100, help="layer for generated polygons") 45 optParser.add_option("--geo", action="store_true", 46 default=False, help="write polgyons with geo-coordinates") 47 optParser.add_option("--internal", action="store_true", 48 default=False, help="include internal edges in generated shapes") 49 optParser.add_option("--spread", type="float", help="spread polygons laterally to avoid overlap") 50 optParser.add_option("--blur", type="float", 51 default=0, help="maximum random disturbance to route geometry") 52 optParser.add_option("--scale-width", type="float", dest="scaleWidth", 53 help="group similar routes and scale width by " + 54 "group size multiplied with the given factor (in m)") 55 optParser.add_option("--standalone", action="store_true", default=False, 56 help="Parse stand-alone routes that are not define as child-element of a vehicle") 57 optParser.add_option("--filter-output.file", dest="filterOutputFile", 58 help="only write output for edges in the given selection file") 59 optParser.add_option("--seed", type="int", help="random seed") 60 options, args = optParser.parse_args(args=args) 61 if options.seed: 62 random.seed(options.seed) 63 if len(args) < 2: 64 sys.exit(USAGE) 65 try: 66 options.net = args[0] 67 options.routefiles = args[1:] 68 options.colorgen = Colorgen( 69 (options.hue, options.saturation, options.brightness)) 70 except Exception: 71 sys.exit(USAGE) 72 if options.outfile is None: 73 options.outfile = options.routefiles[0] + ".poly.xml" 74 75 return options 76 77 78def randomize_pos(pos, blur): 79 return tuple([val + random.uniform(-blur, blur) for val in pos]) 80 81 82MISSING_EDGES = set() 83SPREAD = defaultdict(set) 84SPREAD_MAX = [0] 85 86 87def getSpread(lanes): 88 """find the smalles spread value that is available for all lanes""" 89 cands = [0] 90 for i in range(1, SPREAD_MAX[0] + 2): 91 cands += [i, -i] 92 for i in cands: 93 if all([i not in SPREAD[l] for l in lanes]): 94 SPREAD_MAX[0] = max(SPREAD_MAX[0], i) 95 [SPREAD[l].add(i) for l in lanes] 96 return i 97 else: 98 pass 99 # print(i, [l.getID() for l in lanes]) 100 assert(False) 101 102 103def generate_poly(options, net, id, color, edges, outf, type="route", lineWidth=None, params={}): 104 lanes = [] 105 spread = 0 106 for e in edges: 107 if net.hasEdge(e): 108 lanes.append(net.getEdge(e).getLane(0)) 109 else: 110 if e not in MISSING_EDGES: 111 sys.stderr.write("Warning: unknown edge '%s'\n" % e) 112 MISSING_EDGES.add(e) 113 if not lanes: 114 return 115 if options.internal and len(lanes) > 1: 116 lanes2 = [] 117 preferedCon = -1 118 for i, lane in enumerate(lanes): 119 edge = lane.getEdge() 120 if i == 0: 121 cons = edge.getConnections(lanes[i + 1].getEdge()) 122 if cons: 123 lanes2.append(cons[preferedCon].getFromLane()) 124 else: 125 lanes2.append(lane) 126 else: 127 cons = lanes[i - 1].getEdge().getConnections(edge) 128 if cons: 129 viaID = cons[preferedCon].getViaLaneID() 130 if viaID: 131 via = net.getLane(viaID) 132 lanes2.append(via) 133 cons2 = via.getEdge().getConnections(edge) 134 if cons2: 135 viaID2 = cons2[preferedCon].getViaLaneID() 136 if viaID2: 137 via2 = net.getLane(viaID2) 138 lanes2.append(via2) 139 lanes2.append(cons[preferedCon].getToLane()) 140 else: 141 lanes2.append(lane) 142 lanes = lanes2 143 144 shape = list(itertools.chain(*list(l.getShape() for l in lanes))) 145 if options.spread: 146 spread = getSpread(lanes) 147 if spread: 148 shape = geomhelper.move2side(shape, options.spread * spread) 149 params["spread"] = str(spread) 150 if options.blur > 0: 151 shape = [randomize_pos(pos, options.blur) for pos in shape] 152 153 geoFlag = "" 154 lineWidth = '' if lineWidth is None else ' lineWidth="%s"' % lineWidth 155 if options.geo: 156 shape = [net.convertXY2LonLat(*pos) for pos in shape] 157 geoFlag = ' geo="true"' 158 shapeString = ' '.join('%s,%s' % (x, y) for x, y in shape) 159 close = '/' 160 if params: 161 close = '' 162 outf.write('<poly id="%s" color="%s" layer="%s" type="%s" shape="%s"%s%s%s>\n' % ( 163 id, color, options.layer, type, shapeString, geoFlag, lineWidth, close)) 164 if params: 165 for key, value in params.items(): 166 outf.write(' <param key="%s" value="%s"/>\n' % (key, value)) 167 outf.write('</poly>\n') 168 169 170def filterEdges(edges, keep): 171 if keep is None: 172 return edges 173 else: 174 return [e for e in edges if e in keep] 175 176 177def parseRoutes(options): 178 known_ids = set() 179 180 def unique_id(cand, index=0): 181 cand2 = cand 182 if index > 0: 183 cand2 = "%s#%s" % (cand, index) 184 if cand2 in known_ids: 185 return unique_id(cand, index + 1) 186 else: 187 known_ids.add(cand2) 188 return cand2 189 190 keep = None 191 if options.filterOutputFile is not None: 192 keep = set() 193 for line in open(options.filterOutputFile): 194 if line.startswith('edge:'): 195 keep.add(line.replace('edge:', '').strip()) 196 197 for routefile in options.routefiles: 198 print("parsing %s" % routefile) 199 if options.standalone: 200 for route in parse(routefile, 'route'): 201 # print("found veh", vehicle.id) 202 yield unique_id(route.id), filterEdges(route.edges.split(), keep) 203 else: 204 for vehicle in parse(routefile, 'vehicle'): 205 # print("found veh", vehicle.id) 206 yield unique_id(vehicle.id), filterEdges(vehicle.route[0].edges.split(), keep) 207 208 209def main(args): 210 options = parse_args(args) 211 net = readNet(options.net, withInternal=options.internal) 212 213 with open(options.outfile, 'w') as outf: 214 outf.write('<polygons>\n') 215 if options.scaleWidth is None: 216 for route_id, edges in parseRoutes(options): 217 generate_poly(options, net, route_id, options.colorgen(), edges, outf) 218 else: 219 count = {} 220 for route_id, edges in parseRoutes(options): 221 edges = tuple(edges) 222 if edges in count: 223 count[edges][0] += 1 224 else: 225 count[edges] = [1, route_id] 226 for edges, (n, route_id) in count.items(): 227 width = options.scaleWidth * n 228 params = {'count': str(n)} 229 generate_poly(options, net, route_id, options.colorgen(), edges, outf, lineWidth=width, params=params) 230 231 outf.write('</polygons>\n') 232 233 234if __name__ == "__main__": 235 main(sys.argv[1:]) 236