def _road_point_generator(roads): """ Picks a random point on on a road from roads """ assert len(roads) > 0 # Length of all roads combined total_length = sum(map(lambda road: road.getLength(), roads)) while True: # Select a point on the combined stretch of road distance = random.uniform(0, total_length) # Find the selected road length_sum = 0.0 found_road = False for road in roads: if (length_sum + road.getLength()) >= distance: # Compute the exact point on the selected road # Distance along the road segment remaining = distance - length_sum pos = position_on_edge(road, remaining) yield [pos[0], pos[1], road, remaining] found_road = True break else: length_sum += road.getLength() if not found_road: raise AssertionError( "Failed to pick a road. A distance beyond the last road must have been erroneously " "picked: {} (length sum: {}) (total length: {})".format( distance, length_sum, total_length))
def calc_school_divergence(test: TestInstance, plot: bool) -> List[float]: """ Calculate the divergence between generated and real schools by solving the assignment problem on them and plot these schools and assignments on the network if enabled. :param test: the given TestInstance to test :param plot: whether to plot the city, schools, and assignments :return: the divergence for each assigned school """ net: sumolib.net.Net = sumolib.net.readNet(test.net_file) # Get mean school coordinates for real and generated statistics gen_coords = np.array([ position_on_edge(net.getEdge((xml_school.get("edge"))), float(xml_school.get("pos"))) for xml_school in ET.parse(test.gen_stats_out_file).find("schools").findall("school") ]) real_coords = np.array([ position_on_edge(net.getEdge((xml_school.get("edge"))), float(xml_school.get("pos"))) for xml_school in ET.parse(test.real_stats_file).find("schools").findall("school") ]) # Get euclidean distance between all points in both sets as a cost matrix. # Note that the ordering is seemingly important for linear_sum_assignment to work. # Not ordering causes points in the larger set, larger than the max size of the smaller set to be ignored. if len(real_coords) > len(gen_coords): dist = cdist(gen_coords, real_coords) else: dist = cdist(real_coords, gen_coords) # Solve the assignment problem _, assignment = linear_sum_assignment(dist) if plot: plot_school_assignment(net, test.name, gen_coords, real_coords, assignment) # return list of assigned schools divergence return [ dist[i, assignment[i]] for i in range(0, min(len(gen_coords), len(real_coords))) ]
def write_school_coords(net: sumolib.net.Net, stats: ET.ElementTree, filename): """ Writes all schools' positions found in stats file to a csv called 'filename'. These coordinates can be used for testing, e.g 2d KS tests between generated schools, and real school positions in the city :param net: network file that the schools in stats file is placed on :param stats: stats file parsed with ElementTree containing schools :param filename: name of csv to be written """ # Ensure that output directory exists directory = "school_coordinates" if not os.path.exists(directory): os.mkdir(directory) xml_schools = [ xml_school for xml_school in stats.find("schools").findall("school") ] if xml_schools is None: print( f"Cannot write schools to CSV: no schools found in the generated stats file for {filename}" ) return positions = [] # find all generated school positions, append to positions for gen_school_edge in xml_schools: pos = float(gen_school_edge.get("pos")) edge = net.getEdge(gen_school_edge.get("edge")) positions.append(position_on_edge(edge, pos)) # workaround to append columns to csv file. read old_csv, make a new csv and write to this, rename to old csv when done old_csv = f'{directory}/{filename}-school-coords.csv' new_csv = f'{directory}/{filename}-school-coords-new.csv' # if the old_csv already exists, do as mentioned above if os.path.exists(old_csv): with open(old_csv, 'r') as read_obj, \ open(new_csv, 'w', newline='') as write_obj: csv_reader = csv.reader(read_obj) csv_writer = csv.writer(write_obj) for i, row in enumerate(csv_reader): row.append(positions[i][0]) row.append(positions[i][1]) csv_writer.writerow(row) try: os.rename(new_csv, old_csv) except WindowsError: os.remove(old_csv) os.rename(new_csv, old_csv) else: # if file does not exist, simply write to it file = open(old_csv, 'w', newline='') with file: writer = csv.writer(file) writer.writerows(positions)
def setup_bus_stops(net: sumolib.net.Net, stats: ET.ElementTree, min_distance, k): """ Generates bus stops from net, and writes them into stats. """ logging.debug( f"[bus-stops] Using min_distance: {min_distance}, and k (attempts): {k}" ) edges = net.getEdges() city = stats.getroot() bus_stations = city.find("busStations") seed_bus_stops = [] if bus_stations is None: bus_stations = ET.SubElement(city, "busStations") else: for station in bus_stations.findall("busStation"): assert "edge" in station.attrib, "BusStation isn't placed on an edge" edge_id = station.attrib["edge"] assert "pos" in station.attrib, "BusStation doesn't have a position along the edge" along = float(station.attrib["pos"]) edge = net.getEdge(edge_id) if edge is None: logging.warning( "BusStation in stat file reference edge (id=\"{}\") that doesn't exist in the road " "network".format(edge_id)) continue pos = position_on_edge(edge, along) seed_bus_stops.append([pos[0], pos[1], edge, along]) for i, busstop in enumerate( bus_stop_generator(edges, min_distance, min_distance * 2, k, seeds=seed_bus_stops)): edge = busstop[2] dist_along = busstop[3] ET.SubElement(bus_stations, "busStation", attrib={ "id": str(i), "edge": edge.getID(), "pos": str(dist_along), })
def display_network(net: sumolib.net.Net, stats: ET.ElementTree, max_size: int, centre: Tuple[float, float], network_name: str): """ :param net: the network to display noisemap for :param stats: the stats file describing the network :param max_size: maximum width/height of the resulting image :param centre: the centre of the network for drawing dot :param network_name: the name of the network for drawing in upper-left corner :return: """ # Basics about the city and its size boundary = net.getBoundary() city_size = (boundary[2] - boundary[0], boundary[3] - boundary[1]) # Determine the size of the picture and scalars for scaling the city to the correct size # We might have a very wide city. In this case we want to produce a wide image width_height_relation = city_size[1] / city_size[0] if city_size[0] > city_size[1]: width = max_size height = int(max_size * width_height_relation) else: width = int(max_size / width_height_relation) height = max_size width_scale = width / city_size[0] height_scale = height / city_size[1] def to_png_space(xy: Tuple[float, float]) -> Tuple[float, float]: """ Translate the given city position to a png position """ return (xy[0] - boundary[0]) * width_scale, (xy[1] - boundary[1]) * height_scale # Load pretty fonts for Linux and Windows, falling back to defaults fontsize = max(max_size // 90, 10) try: font = ImageFont.truetype("LiberationMono-Regular.ttf", size=fontsize) except IOError: try: font = ImageFont.truetype("arial.ttf", size=fontsize) except IOError: logging.warning("[render] Could not load font, falling back to default") font = ImageFont.load_default() assert font is not None, "[render] No font loaded, cannot continue" # Make image and prepare for drawing img = Image.new("RGB", (width, height), (255, 255, 255)) draw = ImageDraw.Draw(img, "RGBA") # Draw streets if stats.find("streets") is not None: for street_xml in stats.find("streets").findall("street"): edge = net.getEdge(street_xml.attrib["edge"]) population = float(street_xml.attrib["population"]) industry = float(street_xml.attrib["workPosition"]) green = int(10 + 245 * (1 - industry)) blue = int(10 + 245 * (1 - population)) for pos1, pos2 in [edge.getShape()[i:i + 2] for i in range(0, int(len(edge.getShape()) - 1))]: draw.line((to_png_space(pos1), to_png_space(pos2)), (0, green, blue), int(1.5 + 3.5 * population ** 1.5)) else: logging.warning(f"[render] Could not find any streets in statistics") # Draw city gates if stats.find("cityGates") is not None: for gate_xml in stats.find("cityGates").findall("entrance"): edge = net.getEdge(gate_xml.attrib["edge"]) traffic = max(float(gate_xml.attrib["incoming"]), float(gate_xml.attrib["outgoing"])) x, y = to_png_space(position_on_edge(edge, float(gate_xml.attrib["pos"]))) r = int(max_size / 600 + traffic / 1.3) draw.ellipse((x - r, y - r, x + r, y + r), fill=COLOUR_CITY_GATE) else: logging.warning(f"[render] Could not find any city-gates in statistics") # Draw bus stops if stats.find("busStations") is not None: for stop_xml in stats.find("busStations").findall("busStation"): edge = net.getEdge(stop_xml.attrib["edge"]) x, y = to_png_space(position_on_edge(edge, float(stop_xml.attrib["pos"]))) r = max_size / 600 draw.ellipse((x - r, y - r, x + r, y + r), fill=COLOUR_BUS_STOP) else: logging.warning(f"[render] Could not find any bus-stations in statistics") # Draw schools if stats.find("schools") is not None: for school_xml in stats.find("schools").findall("school"): edge = net.getEdge(school_xml.attrib["edge"]) capacity = int(school_xml.get('capacity')) x, y = to_png_space(position_on_edge(edge, float(school_xml.get('pos')))) r = int((max_size / 275 * (capacity / 500) ** 0.4) * 1.1) draw.ellipse((x - r, y - r, x + r, y + r), fill=COLOUR_SCHOOL) else: logging.warning(f"[render] Could not find any schools in statistics") if not any([stats.find(x) for x in {"streets", "cityGates", "busStations", "schools"}]): logging.error("[render] No elements found in statistics, cannot display network and features") exit(1) # Draw city centre x, y = to_png_space(centre) r = max_size / 100 draw.ellipse((x - r, y - r, x + r, y + r), fill=COLOUR_CENTRE) # Flip image on the horizontal axis and update draw-pointer img = img.transpose(FLIP_TOP_BOTTOM) draw = ImageDraw.Draw(img, "RGBA") Legend(max_size, height, draw, font) \ .draw_network_name(network_name) \ .draw_scale_legend(city_size, width_scale) \ .draw_gradient("Pop, work gradient") \ .draw_icon_legend(COLOUR_CENTRE, "Centre") \ .draw_icon_legend(COLOUR_SCHOOL, "School") \ .draw_icon_legend(COLOUR_BUS_STOP, "Bus stop") \ .draw_icon_legend(COLOUR_CITY_GATE, "City gate") img.show()
# base of file name, e.g. "vejen.trips.rou.xml" -> "vejen" fname = os.path.basename(args["--trips-file"]) while "." in fname: fname = os.path.splitext(fname)[0] data = [] with open(os.path.dirname(args["--trips-file"]) + f"/{fname}-trip-starts.csv", "w", newline="") as csv_starts: writer_starts = csv.writer(csv_starts) for trip_xml in trips.findall("trip"): edge = net.getEdge(trip_xml.get("from")) departTime = float(trip_xml.get("depart")) departPos = float(trip_xml.get("departPos") or edge.getLength() * random.random()) x, y = position_on_edge(edge, departPos) x -= offset_x y -= offset_y datapoint = (x, y, departTime) writer_starts.writerow(datapoint) data.append(datapoint) if args["--png"] or args["--gif"]: # Calculate dimensions and scaling max_size = 800 width_height_relation = net_height / net_width if net_width > net_height: width = max_size height = int(max_size * width_height_relation) else: width = int(max_size / width_height_relation)