eclipse-sumo / sumo

Eclipse SUMO is an open source, highly portable, microscopic and continuous traffic simulation package designed to handle large networks. It allows for intermodal simulation including pedestrians and comes with a large set of tools for scenario creation.
https://eclipse.dev/sumo
Eclipse Public License 2.0
2.5k stars 1.41k forks source link

Validation Request: Polyline to Route Converter for SUMO #15408

Open dominique-AR opened 3 weeks ago

dominique-AR commented 3 weeks ago

Hi everyone, @bcoueraud87 @namdre 😃

Description:

I've developed a Python script to convert polylines in XML format to routes with edges using a SUMO network definition. I'd like to request a review of this code to ensure it's correct and follows best practices for working with SUMO.

Code Overview:

The script does the following:

  1. Parses command-line arguments for input files (network and polyline XML) and output file.
  2. Loads a SUMO network file.
  3. Parses an XML file containing polylines.
  4. For each polyline, finds the nearest edges in the network to create a route.
  5. Writes the resulting routes to an output XML file.

Key ### Objects and Functions:

Questions:

  1. Is the approach of finding the closest edge for each point in the polyline correct? Are there potential issues with this method?
  2. Are there any SUMO-specific considerations or functions I should be using that I've overlooked?
  3. Is the use of sumolib appropriate and efficient?
  4. Are there any edge cases or scenarios where this script might fail or produce incorrect results?
  5. Is the XML parsing and writing implemented correctly?

Additional Context:

This script is intended to be a reverse operation of the route2poly.py script provided in the SUMO tools. Any insights on how to make it more consistent with SUMO's approach would be greatly appreciated.

Here the code :

`#!/usr/bin/env python

import sys
import os
from optparse import OptionParser
from xml.etree import ElementTree as ET

if 'SUMO_HOME' in os.environ:
    sys.path.append(os.path.join(os.environ['SUMO_HOME'], 'tools'))
else:
    sys.exit("Please declare environment variable 'SUMO_HOME'")

import sumolib

def parse_args():
    parser = OptionParser()
    parser.add_option("-n", "--net-file", dest="netfile",
                      help="SUMO network file", metavar="FILE")
    parser.add_option("-p", "--poly-file", dest="polyfile",
                      help="Input polyline XML file", metavar="FILE")
    parser.add_option("-o", "--output-file", dest="outfile",
                      help="Output route file", metavar="FILE")
    parser.add_option("-t", "--threshold", dest="threshold", type="float", default=10,
                      help="Distance threshold for edge matching (default: 10 meters)")

    (options, args) = parser.parse_args()
    if not options.netfile or not options.polyfile or not options.outfile:
        parser.error("All three files (net, poly, and output) are required.")
    return options

def load_net(netfile):
    return sumolib.net.readNet(netfile)

def find_closest_edge(net, point, threshold):
    x, y = point
    edges = net.getNeighboringEdges(x, y, threshold)
    if not edges:
        return None
    return min(edges, key=lambda x: x[1])[0]

def polyline_to_route(net, shape, threshold):
    route = []
    for point in shape:
        edge = find_closest_edge(net, point, threshold)
        if edge and (not route or edge != route[-1]):
            route.append(edge)
    return route

def parse_polylines(polyfile):
    tree = ET.parse(polyfile)
    root = tree.getroot()
    polylines = {}
    for poly in root.findall('poly'):
        id = poly.get('id')
        shape = poly.get('shape')
        points = [tuple(map(float, p.split(','))) for p in shape.split()]
        polylines[id] = points
    return polylines

def main():
    options = parse_args()
    net = load_net(options.netfile)
    polylines = parse_polylines(options.polyfile)

    with open(options.outfile, 'w') as outf:
        outf.write('<routes>\n')
        for poly_id, shape in polylines.items():
            route = polyline_to_route(net, shape, options.threshold)
            if route:
                edge_ids = ' '.join(edge.getID() for edge in route)
                outf.write(f'    <route id="{poly_id}" edges="{edge_ids}"/>\n')
            else:
                print(f"Warning: Could not create route for polyline {poly_id}")
        outf.write('</routes>\n')

    print(f"Routes have been written to {options.outfile}")

if __name__ == "__main__":
    main()`
behrisch commented 1 week ago

@bcoueraud87 for you

bcoueraud87 commented 1 week ago

@behrisch Please assign me