Ejemplo n.º 1
0
    def __init__(self, nIntersections, p=[0.2, 0.7, 0.1, 0.1], startId=0, seed=0, mapSize=(500, 500), cellSize=(100, 100), debug=True) -> None:

        self.nIntersections = nIntersections
        self.p = p # probability distribution of 3-way, 4, 5, 6
        self.intersections = {} # DirectionIntersection -> intersection
        self.placedIntersections = {}
        self.rotation = {}
        self.nextIntersectionId = startId
        
        self.seed = seed
        self.builder = SequentialJunctionBuilder(
                                                    minAngle=np.pi/6, 
                                                    maxAngle=np.pi * .75,
                                                    straightRoadLen=1, 
                                                    probLongConnection=0.3,
                                                    probMinAngle=0.1,
                                                    probRestrictedLane=0,
                                                    maxConnectionLength=30,
                                                    minConnectionLength=12,
                                                    random_seed=self.seed)

        self.moreThan4Builder = SequentialJunctionBuilder(
                                                    minAngle=np.pi/10, 
                                                    maxAngle=np.pi * .75,
                                                    straightRoadLen=1, 
                                                    probLongConnection=0.5,
                                                    probMinAngle=0.5,
                                                    probRestrictedLane=0,
                                                    maxConnectionLength=50,
                                                    minConnectionLength=20,
                                                    random_seed=self.seed)


        self.validator = IntersectionValidator()
        self.intersectionAdapter = IntersectionAdapter()
        
        self.grid = Grid(size=mapSize, cellSize=cellSize)

        self.mapBuilder = MapBuilder(self.grid, [], random_seed=40)

        self.network = None

        self.debug = debug

        self.name = "HDMapBuilder"

        self.nextRoadId = 0

        pass
Ejemplo n.º 2
0
    def __init__(self, 
                outputDir, 
                outputPrefix, 
                lastId=0, 
                minAngle = np.pi / 10, 
                maxAngle = np.pi, 
                straightRoadLen = 10,
                esminiPath = None, 
                saveImage = True):
        """The angle between two connected roads are >= self.minAngle <= self.maxAngle

        Args:
            outputDir ([type]): [description]
            outputPrefix ([type]): [description]
            lastId (int, optional): [description]. Defaults to 0.
            minAngle ([type], optional): [description]. Defaults to np.pi/10.
            maxAngle ([type], optional): [description]. Defaults to np.pi.
            straightRoadLen : used both for normal and connection roads
            esminiPath: Path to esmini to generate images for roads.
        """

        self.destinationPrefix = os.path.join(outputDir, outputPrefix)
        self.minAngle = minAngle
        self.maxAngle = maxAngle
        self.lastId = lastId
        self.straightRoadLen = straightRoadLen

        self.roadBuilder = RoadBuilder()
        self.straightRoadBuilder = StraightRoadBuilder()

        self.junctionBuilder = JunctionBuilder(self.roadBuilder)
        self.sequentialJunctionBuilder = SequentialJunctionBuilder(self.roadBuilder, self.straightRoadLen, minAngle=minAngle, maxAngle=maxAngle)

        self.junctionMerger = JunctionMerger(outputDir, outputPrefix, lastId)

        self.configuration = Configuration()

        if esminiPath is None:
            self.esminiPath = self.configuration.get("esminipath")
        else:
            self.esminiPath = esminiPath

        self.saveImage = saveImage

        if os.path.isdir(self.esminiPath) is False:
            logging.warn(f"Esmini path not found {self.esminiPath}. Will break if you try to save images using harvester.")

        pass
    def setUp(self):

        self.configuration = Configuration()
        outputDir = os.path.join(os.getcwd(), 'output')
        lastId = 0
        self.seed = 2
        self.builder = SequentialJunctionBuilder(minAngle=np.pi / 4,
                                                 maxAngle=np.pi * .75,
                                                 straightRoadLen=10,
                                                 probLongConnection=0.5,
                                                 probMinAngle=0.5,
                                                 probRestrictedLane=0.2,
                                                 maxConnectionLength=30,
                                                 minConnectionLength=12,
                                                 random_seed=self.seed)

        self.randomState = self.configuration.get("random_state")

        pass
class test_Intersection(unittest.TestCase):
    def setUp(self):

        self.configuration = Configuration()
        outputDir = os.path.join(os.getcwd(), 'output')
        lastId = 0
        self.seed = 2
        self.builder = SequentialJunctionBuilder(minAngle=np.pi / 4,
                                                 maxAngle=np.pi * .75,
                                                 straightRoadLen=10,
                                                 probLongConnection=0.5,
                                                 probMinAngle=0.5,
                                                 probRestrictedLane=0.2,
                                                 maxConnectionLength=30,
                                                 minConnectionLength=12,
                                                 random_seed=self.seed)

        self.randomState = self.configuration.get("random_state")

        pass

    def test_getIncidentPoints(self):

        maxNumberOfRoadsPerJunction = 4
        path = self.configuration.get("harvested_straight_roads")
        intersection = self.builder.createWithRandomLaneConfigurations(
            path,
            0,
            maxNumberOfRoadsPerJunction=maxNumberOfRoadsPerJunction,
            maxLanePerSide=2,
            minLanePerSide=0,
            internalConnections=True,
            cp1=pyodrx.ContactPoint.end,
            internalLinkStrategy=LaneConfigurationStrategies.SPLIT_FIRST,
            getAsOdr=False)

        odr = intersection.odr

        xmlPath = f"output/test_getIncidentPoints-split-first-{maxNumberOfRoadsPerJunction}.xodr"
        odr.write_xml(xmlPath)

        incidentPoints = intersection.getIncidentPoints()
        translatedPoints = intersection.getIncidentPointsTranslatedToCenter()
        print(f"width and height: {intersection.getWH()}")
        print(incidentPoints)
        print(translatedPoints)

        extensions.printRoadPositions(odr)

        extensions.view_road(
            odr, os.path.join('..', self.configuration.get("esminipath")))
Ejemplo n.º 5
0
class test_SequentialJunctionBuilder(unittest.TestCase):
    def setUp(self):

        self.configuration = Configuration()
        outputDir = os.path.join(os.getcwd(), 'output')
        lastId = 0
        self.seed = 2
        self.builder = SequentialJunctionBuilder(minAngle=np.pi / 10,
                                                 maxAngle=np.pi * .75,
                                                 straightRoadLen=5,
                                                 probLongConnection=0.5,
                                                 probMinAngle=0.5,
                                                 probRestrictedLane=0.2,
                                                 maxConnectionLength=50,
                                                 minConnectionLength=20,
                                                 random_seed=self.seed)

        self.randomState = self.configuration.get("random_state")
        self.validator = IntersectionValidator()

        pass

    def test_drawLikeAPainter2L(self):
        maxNumberOfRoadsPerJunction = 3
        odr = self.builder.drawLikeAPainter2L(
            odrId=0, maxNumberOfRoadsPerJunction=maxNumberOfRoadsPerJunction)
        extensions.printRoadPositions(odr)
        extensions.view_road(
            odr, os.path.join('..', self.configuration.get("esminipath")))

        xmlPath = f"output/test_drawLikeAPainter2L-{maxNumberOfRoadsPerJunction}.xodr"
        odr.write_xml(xmlPath)

        maxNumberOfRoadsPerJunction = 4
        odr = self.builder.drawLikeAPainter2L(
            odrId=0, maxNumberOfRoadsPerJunction=maxNumberOfRoadsPerJunction)
        extensions.printRoadPositions(odr)
        extensions.view_road(
            odr, os.path.join('..', self.configuration.get("esminipath")))

        xmlPath = f"output/test_drawLikeAPainter2L-{maxNumberOfRoadsPerJunction}.xodr"
        odr.write_xml(xmlPath)

        maxNumberOfRoadsPerJunction = 5
        odr = self.builder.drawLikeAPainter2L(
            odrId=0, maxNumberOfRoadsPerJunction=maxNumberOfRoadsPerJunction)
        extensions.printRoadPositions(odr)
        extensions.view_road(
            odr, os.path.join('..', self.configuration.get("esminipath")))

        xmlPath = f"output/test_drawLikeAPainter2L-{maxNumberOfRoadsPerJunction}.xodr"
        odr.write_xml(xmlPath)

        maxNumberOfRoadsPerJunction = 6
        odr = self.builder.drawLikeAPainter2L(
            odrId=0, maxNumberOfRoadsPerJunction=maxNumberOfRoadsPerJunction)
        extensions.printRoadPositions(odr)
        extensions.view_road(
            odr, os.path.join('..', self.configuration.get("esminipath")))

        xmlPath = f"output/test_drawLikeAPainter2L-{maxNumberOfRoadsPerJunction}.xodr"
        odr.write_xml(xmlPath)

    def test_drawLikeAPainter2LWihtoutInternalConnections(self):
        maxNumberOfRoadsPerJunction = 5
        odr = self.builder.drawLikeAPainter2L(
            odrId=0,
            maxNumberOfRoadsPerJunction=maxNumberOfRoadsPerJunction,
            internalConnections=False)
        extensions.printRoadPositions(odr)
        extensions.view_road(
            odr, os.path.join('..', self.configuration.get("esminipath")))
        xmlPath = f"output/test_drawLikeAPainter2LWihtoutInternalConnections.xodr"
        odr.write_xml(xmlPath)

    def test_createWithRandomLaneConfigurations(self):

        maxNumberOfRoadsPerJunction = 4
        path = self.configuration.get("harvested_straight_roads")
        odr = self.builder.createWithRandomLaneConfigurations(
            path,
            0,
            maxNumberOfRoadsPerJunction=maxNumberOfRoadsPerJunction,
            maxLanePerSide=2,
            minLanePerSide=0,
            internalConnections=True,
            cp1=pyodrx.ContactPoint.end,
            internalLinkStrategy=LaneConfigurationStrategies.SPLIT_FIRST,
            restrictedLanes=True)

        xmlPath = f"output/test_createWithRandomLaneConfigurations-split-first-{maxNumberOfRoadsPerJunction}.xodr"
        odr.write_xml(xmlPath)

        extensions.view_road(
            odr, os.path.join('..', self.configuration.get("esminipath")))

        maxNumberOfRoadsPerJunction = 3
        path = self.configuration.get("harvested_straight_roads")
        odr = self.builder.createWithRandomLaneConfigurations(
            path,
            0,
            maxNumberOfRoadsPerJunction=maxNumberOfRoadsPerJunction,
            maxLanePerSide=2,
            minLanePerSide=0,
            internalConnections=True,
            cp1=pyodrx.ContactPoint.end,
            internalLinkStrategy=LaneConfigurationStrategies.SPLIT_LAST,
            uTurnLanes=1,
            restrictedLanes=True)

        extensions.view_road(
            odr, os.path.join('..', self.configuration.get("esminipath")))
        xmlPath = f"output/test_createWithRandomLaneConfigurations-split-last-{maxNumberOfRoadsPerJunction}.xodr"
        odr.write_xml(xmlPath)

        maxNumberOfRoadsPerJunction = 5
        path = self.configuration.get("harvested_straight_roads")
        odr = self.builder.createWithRandomLaneConfigurations(
            path,
            0,
            maxNumberOfRoadsPerJunction=maxNumberOfRoadsPerJunction,
            maxLanePerSide=2,
            minLanePerSide=0,
            internalConnections=True,
            cp1=pyodrx.ContactPoint.end,
            internalLinkStrategy=LaneConfigurationStrategies.SPLIT_ANY,
            restrictedLanes=True)

        extensions.view_road(
            odr, os.path.join('..', self.configuration.get("esminipath")))
        xmlPath = f"output/test_createWithRandomLaneConfigurations-split-any-{maxNumberOfRoadsPerJunction}.xodr"
        odr.write_xml(xmlPath)

        maxNumberOfRoadsPerJunction = 6
        path = self.configuration.get("harvested_straight_roads")
        odr = self.builder.createWithRandomLaneConfigurations(
            path,
            0,
            maxNumberOfRoadsPerJunction=maxNumberOfRoadsPerJunction,
            maxLanePerSide=2,
            minLanePerSide=0,
            internalConnections=True,
            cp1=pyodrx.ContactPoint.end,
            internalLinkStrategy=LaneConfigurationStrategies.SPLIT_ANY)

        extensions.view_road(
            odr, os.path.join('..', self.configuration.get("esminipath")))
        xmlPath = f"output/test_createWithRandomLaneConfigurations-split-any-{maxNumberOfRoadsPerJunction}.xodr"
        odr.write_xml(xmlPath)

        maxNumberOfRoadsPerJunction = 8
        path = self.configuration.get("harvested_straight_roads")
        odr = self.builder.createWithRandomLaneConfigurations(
            path,
            0,
            maxNumberOfRoadsPerJunction=maxNumberOfRoadsPerJunction,
            maxLanePerSide=2,
            minLanePerSide=0,
            internalConnections=True,
            cp1=pyodrx.ContactPoint.end,
            internalLinkStrategy=LaneConfigurationStrategies.SPLIT_ANY)

        extensions.view_road(
            odr, os.path.join('..', self.configuration.get("esminipath")))
        xmlPath = f"output/test_createWithRandomLaneConfigurations-split-any-{maxNumberOfRoadsPerJunction}.xodr"
        odr.write_xml(xmlPath)

    def test_equalAngles(self):

        maxNumberOfRoadsPerJunction = 4
        path = self.configuration.get("harvested_straight_roads")
        odr = self.builder.createWithRandomLaneConfigurations(
            path,
            0,
            maxNumberOfRoadsPerJunction=maxNumberOfRoadsPerJunction,
            maxLanePerSide=2,
            minLanePerSide=0,
            internalConnections=True,
            cp1=pyodrx.ContactPoint.end,
            internalLinkStrategy=LaneConfigurationStrategies.SPLIT_ANY,
            equalAngles=True)

        extensions.view_road(
            odr, os.path.join('..', self.configuration.get("esminipath")))
        xmlPath = f"output/test_createWithRandomLaneConfigurations-split-any-{maxNumberOfRoadsPerJunction}.xodr"
        odr.write_xml(xmlPath)

        maxNumberOfRoadsPerJunction = 4
        path = self.configuration.get("harvested_straight_roads")
        odr = self.builder.createWithRandomLaneConfigurations(
            path,
            0,
            maxNumberOfRoadsPerJunction=maxNumberOfRoadsPerJunction,
            maxLanePerSide=2,
            minLanePerSide=0,
            internalConnections=True,
            cp1=pyodrx.ContactPoint.end,
            internalLinkStrategy=LaneConfigurationStrategies.SPLIT_ANY,
            equalAngles=True)

        extensions.view_road(
            odr, os.path.join('..', self.configuration.get("esminipath")))
        xmlPath = f"output/test_createWithRandomLaneConfigurations-split-any-{maxNumberOfRoadsPerJunction}.xodr"
        odr.write_xml(xmlPath)

        maxNumberOfRoadsPerJunction = 3
        path = self.configuration.get("harvested_straight_roads")
        odr = self.builder.createWithRandomLaneConfigurations(
            path,
            0,
            maxNumberOfRoadsPerJunction=maxNumberOfRoadsPerJunction,
            maxLanePerSide=2,
            minLanePerSide=0,
            internalConnections=True,
            cp1=pyodrx.ContactPoint.end,
            internalLinkStrategy=LaneConfigurationStrategies.SPLIT_ANY,
            equalAngles=True)

        extensions.view_road(
            odr, os.path.join('..', self.configuration.get("esminipath")))
        xmlPath = f"output/test_createWithRandomLaneConfigurations-split-any-{maxNumberOfRoadsPerJunction}.xodr"
        odr.write_xml(xmlPath)

        maxNumberOfRoadsPerJunction = 5
        path = self.configuration.get("harvested_straight_roads")
        odr = self.builder.createWithRandomLaneConfigurations(
            path,
            0,
            maxNumberOfRoadsPerJunction=maxNumberOfRoadsPerJunction,
            maxLanePerSide=2,
            minLanePerSide=0,
            internalConnections=True,
            cp1=pyodrx.ContactPoint.end,
            internalLinkStrategy=LaneConfigurationStrategies.SPLIT_ANY,
            equalAngles=True)

        extensions.view_road(
            odr, os.path.join('..', self.configuration.get("esminipath")))
        xmlPath = f"output/test_createWithRandomLaneConfigurations-split-any-{maxNumberOfRoadsPerJunction}.xodr"
        odr.write_xml(xmlPath)

        maxNumberOfRoadsPerJunction = 6
        path = self.configuration.get("harvested_straight_roads")
        odr = self.builder.createWithRandomLaneConfigurations(
            path,
            0,
            maxNumberOfRoadsPerJunction=maxNumberOfRoadsPerJunction,
            maxLanePerSide=2,
            minLanePerSide=0,
            internalConnections=True,
            cp1=pyodrx.ContactPoint.end,
            internalLinkStrategy=LaneConfigurationStrategies.SPLIT_ANY,
            equalAngles=True)

        extensions.view_road(
            odr, os.path.join('..', self.configuration.get("esminipath")))
        xmlPath = f"output/test_createWithRandomLaneConfigurations-split-any-{maxNumberOfRoadsPerJunction}.xodr"
        odr.write_xml(xmlPath)

    def test_6PlusLanes(self):

        maxNumberOfRoadsPerJunction = 7
        path = self.configuration.get("harvested_straight_roads")
        odr = self.builder.createWithRandomLaneConfigurations(
            path,
            0,
            maxNumberOfRoadsPerJunction=maxNumberOfRoadsPerJunction,
            maxLanePerSide=2,
            minLanePerSide=0,
            internalConnections=True,
            cp1=pyodrx.ContactPoint.end,
            internalLinkStrategy=LaneConfigurationStrategies.SPLIT_ANY)

        extensions.view_road(
            odr, os.path.join('..', self.configuration.get("esminipath")))
        xmlPath = f"output/test_createWithRandomLaneConfigurations-split-any-{maxNumberOfRoadsPerJunction}.xodr"
        odr.write_xml(xmlPath)

        maxNumberOfRoadsPerJunction = 8
        path = self.configuration.get("harvested_straight_roads")
        odr = self.builder.createWithRandomLaneConfigurations(
            path,
            0,
            maxNumberOfRoadsPerJunction=maxNumberOfRoadsPerJunction,
            maxLanePerSide=2,
            minLanePerSide=0,
            internalConnections=True,
            cp1=pyodrx.ContactPoint.end,
            internalLinkStrategy=LaneConfigurationStrategies.SPLIT_ANY)

        extensions.view_road(
            odr, os.path.join('..', self.configuration.get("esminipath")))
        xmlPath = f"output/test_createWithRandomLaneConfigurations-split-any-{maxNumberOfRoadsPerJunction}.xodr"
        odr.write_xml(xmlPath)

        maxNumberOfRoadsPerJunction = 8
        path = self.configuration.get("harvested_straight_roads")
        odr = self.builder.createWithRandomLaneConfigurations(
            path,
            0,
            maxNumberOfRoadsPerJunction=maxNumberOfRoadsPerJunction,
            maxLanePerSide=2,
            minLanePerSide=0,
            internalConnections=True,
            cp1=pyodrx.ContactPoint.end,
            internalLinkStrategy=LaneConfigurationStrategies.SPLIT_ANY,
            equalAngles=True)

        extensions.view_road(
            odr, os.path.join('..', self.configuration.get("esminipath")))
        xmlPath = f"output/test_createWithRandomLaneConfigurations-split-any-{maxNumberOfRoadsPerJunction}.xodr"
        odr.write_xml(xmlPath)

        maxNumberOfRoadsPerJunction = 8
        path = self.configuration.get("harvested_straight_roads")
        odr = self.builder.createWithRandomLaneConfigurations(
            path,
            0,
            maxNumberOfRoadsPerJunction=maxNumberOfRoadsPerJunction,
            maxLanePerSide=2,
            minLanePerSide=0,
            internalConnections=True,
            cp1=pyodrx.ContactPoint.end,
            internalLinkStrategy=LaneConfigurationStrategies.SPLIT_ANY)

        extensions.view_road(
            odr, os.path.join('..', self.configuration.get("esminipath")))
        xmlPath = f"output/test_createWithRandomLaneConfigurations-split-any-{maxNumberOfRoadsPerJunction}.xodr"
        odr.write_xml(xmlPath)

        maxNumberOfRoadsPerJunction = 9
        path = self.configuration.get("harvested_straight_roads")
        odr = self.builder.createWithRandomLaneConfigurations(
            path,
            0,
            maxNumberOfRoadsPerJunction=maxNumberOfRoadsPerJunction,
            maxLanePerSide=2,
            minLanePerSide=0,
            internalConnections=True,
            cp1=pyodrx.ContactPoint.end,
            internalLinkStrategy=LaneConfigurationStrategies.SPLIT_ANY)

        extensions.view_road(
            odr, os.path.join('..', self.configuration.get("esminipath")))
        xmlPath = f"output/test_createWithRandomLaneConfigurations-split-any-{maxNumberOfRoadsPerJunction}.xodr"
        odr.write_xml(xmlPath)

    def test_TJunctions(self):

        maxNumberOfRoadsPerJunction = 3
        maxLanePerSide = 1
        minLanePerSide = 1

        for sl in range(5):
            path = self.configuration.get("harvested_straight_roads")
            odr = self.builder.createWithRandomLaneConfigurations(
                path,
                0,
                maxNumberOfRoadsPerJunction=maxNumberOfRoadsPerJunction,
                maxLanePerSide=maxLanePerSide,
                minLanePerSide=minLanePerSide,
                internalConnections=True,
                cp1=pyodrx.ContactPoint.end,
                internalLinkStrategy=LaneConfigurationStrategies.SPLIT_ANY,
                uTurnLanes=0)

            # xmlPath = f"output/test_createWithRandomLaneConfigurations-split-any-{maxNumberOfRoadsPerJunction}-{sl}.xodr"
            xmlPath = f"output/seed-{self.seed}-{maxNumberOfRoadsPerJunction}-way-{sl}.xodr"
            odr.write_xml(xmlPath)

            extensions.view_road(
                odr, os.path.join('..', self.configuration.get("esminipath")))
            extensions.saveRoadImageFromFile(
                xmlPath, self.configuration.get("esminipath"))

    def test_4WayJunctions(self):

        maxNumberOfRoadsPerJunction = 4
        maxLanePerSide = 1
        minLanePerSide = 1

        for sl in range(5):
            path = self.configuration.get("harvested_straight_roads")
            intersection = self.builder.createWithRandomLaneConfigurations(
                path,
                sl,
                maxNumberOfRoadsPerJunction=maxNumberOfRoadsPerJunction,
                maxLanePerSide=maxLanePerSide,
                minLanePerSide=minLanePerSide,
                internalConnections=True,
                cp1=pyodrx.ContactPoint.end,
                internalLinkStrategy=LaneConfigurationStrategies.SPLIT_ANY,
                getAsOdr=False)

            odr = intersection.odr
            # xmlPath = f"output/test_createWithRandomLaneConfigurations-split-any-{maxNumberOfRoadsPerJunction}-{sl}.xodr"
            xmlPath = f"output/seed-{self.seed}-{maxNumberOfRoadsPerJunction}-way-{sl}.xodr"
            odr.write_xml(xmlPath)
            isValid = self.validator.validateIncidentPoints(
                intersection, self.builder.minConnectionLength)
            # if isValid == False:
            #     print(f"{sl} is an invalid intersection")
            plt = extensions.view_road(
                odr,
                os.path.join('..', self.configuration.get("esminipath")),
                returnPlt=True)
            if isValid == False:
                plt.title("Invalid")
            else:
                plt.title("Valid")
            plt.show()
            extensions.saveRoadImageFromFile(
                xmlPath, self.configuration.get("esminipath"))

    def test_5WayJunctions(self):

        maxNumberOfRoadsPerJunction = 5
        maxLanePerSide = 3
        minLanePerSide = 0

        for sl in range(5):
            path = self.configuration.get("harvested_straight_roads")
            intersection = self.builder.createWithRandomLaneConfigurations(
                path,
                sl,
                maxNumberOfRoadsPerJunction=maxNumberOfRoadsPerJunction,
                maxLanePerSide=maxLanePerSide,
                minLanePerSide=minLanePerSide,
                internalConnections=True,
                cp1=pyodrx.ContactPoint.end,
                internalLinkStrategy=LaneConfigurationStrategies.SPLIT_ANY,
                getAsOdr=False)

            odr = intersection.odr
            # xmlPath = f"output/test_createWithRandomLaneConfigurations-split-any-{maxNumberOfRoadsPerJunction}-{sl}.xodr"
            xmlPath = f"output/seed-{self.seed}-{maxNumberOfRoadsPerJunction}-way-{sl}.xodr"
            odr.write_xml(xmlPath)
            isValid = self.validator.validateIncidentPoints(
                intersection, self.builder.minConnectionLength)
            # if isValid == False:
            #     print(f"{sl} is an invalid intersection")
            plt = extensions.view_road(
                odr,
                os.path.join('..', self.configuration.get("esminipath")),
                returnPlt=True)
            if isValid == False:
                plt.title("Invalid")
            else:
                plt.title("Valid")
            plt.show()
            extensions.saveRoadImageFromFile(
                xmlPath, self.configuration.get("esminipath"))

    def test_6WayJunctions(self):

        maxNumberOfRoadsPerJunction = 6
        maxLanePerSide = 2
        minLanePerSide = 0

        for sl in range(5):
            path = self.configuration.get("harvested_straight_roads")
            intersection = self.builder.createWithRandomLaneConfigurations(
                path,
                sl,
                maxNumberOfRoadsPerJunction=maxNumberOfRoadsPerJunction,
                maxLanePerSide=maxLanePerSide,
                minLanePerSide=minLanePerSide,
                internalConnections=True,
                cp1=pyodrx.ContactPoint.end,
                internalLinkStrategy=LaneConfigurationStrategies.SPLIT_ANY,
                getAsOdr=False)

            odr = intersection.odr
            # xmlPath = f"output/test_createWithRandomLaneConfigurations-split-any-{maxNumberOfRoadsPerJunction}-{sl}.xodr"
            xmlPath = f"output/seed-{self.seed}-{maxNumberOfRoadsPerJunction}-way-{sl}.xodr"
            odr.write_xml(xmlPath)
            extensions.printRoadPositions(odr)
            isValid = self.validator.validateIncidentPoints(
                intersection, self.builder.minConnectionLength)
            # if isValid == False:
            #     print(f"{sl} is an invalid intersection")
            plt = extensions.view_road(
                odr,
                os.path.join('..', self.configuration.get("esminipath")),
                returnPlt=True)
            if isValid == False:
                plt.title(f"Invalid - {xmlPath}")
            else:
                plt.title(f"Valid - {xmlPath}")
            plt.show()
            extensions.saveRoadImageFromFile(
                xmlPath, self.configuration.get("esminipath"))

    def test_7WayJunctions(self):

        maxNumberOfRoadsPerJunction = 7
        maxLanePerSide = 2
        minLanePerSide = 0

        for sl in range(5):
            path = self.configuration.get("harvested_straight_roads")
            intersection = self.builder.createWithRandomLaneConfigurations(
                path,
                sl,
                maxNumberOfRoadsPerJunction=maxNumberOfRoadsPerJunction,
                maxLanePerSide=maxLanePerSide,
                minLanePerSide=minLanePerSide,
                internalConnections=True,
                cp1=pyodrx.ContactPoint.end,
                internalLinkStrategy=LaneConfigurationStrategies.SPLIT_ANY,
                getAsOdr=False)

            odr = intersection.odr
            # xmlPath = f"output/test_createWithRandomLaneConfigurations-split-any-{maxNumberOfRoadsPerJunction}-{sl}.xodr"
            xmlPath = f"output/seed-{self.seed}-{maxNumberOfRoadsPerJunction}-way-{sl}.xodr"
            odr.write_xml(xmlPath)
            extensions.printRoadPositions(odr)
            isValid = self.validator.validateIncidentPoints(
                intersection, self.builder.minConnectionLength)
            # if isValid == False:
            #     print(f"{sl} is an invalid intersection")
            plt = extensions.view_road(
                odr,
                os.path.join('..', self.configuration.get("esminipath")),
                returnPlt=True)
            if isValid == False:
                plt.title(f"Invalid - {xmlPath}")
            else:
                plt.title(f"Valid - {xmlPath}")
            plt.show()
            extensions.saveRoadImageFromFile(
                xmlPath, self.configuration.get("esminipath"))

    def test_7WayJunctionsEQA(self):

        maxNumberOfRoadsPerJunction = 7
        maxLanePerSide = 2
        minLanePerSide = 0

        for sl in range(5):
            path = self.configuration.get("harvested_straight_roads")
            intersection = self.builder.createWithRandomLaneConfigurations(
                path,
                sl,
                maxNumberOfRoadsPerJunction=maxNumberOfRoadsPerJunction,
                maxLanePerSide=maxLanePerSide,
                minLanePerSide=minLanePerSide,
                internalConnections=True,
                cp1=pyodrx.ContactPoint.end,
                internalLinkStrategy=LaneConfigurationStrategies.SPLIT_ANY,
                equalAngles=True,
                getAsOdr=False)

            odr = intersection.odr
            # xmlPath = f"output/test_createWithRandomLaneConfigurations-split-any-{maxNumberOfRoadsPerJunction}-{sl}.xodr"
            xmlPath = f"output/seed-{self.seed}-{maxNumberOfRoadsPerJunction}-way-{sl}.xodr"
            odr.write_xml(xmlPath)
            extensions.printRoadPositions(odr)
            isValid = self.validator.validateIncidentPoints(
                intersection, self.builder.minConnectionLength)
            # if isValid == False:
            #     print(f"{sl} is an invalid intersection")
            plt = extensions.view_road(
                odr,
                os.path.join('..', self.configuration.get("esminipath")),
                returnPlt=True)
            if isValid == False:
                plt.title(f"Invalid - {xmlPath}")
            else:
                plt.title(f"Valid - {xmlPath}")
            plt.show()
            extensions.saveRoadImageFromFile(
                xmlPath, self.configuration.get("esminipath"))

    def test_8PlusWayJunctions(self):

        maxNumberOfRoadsPerJunction = 20
        maxLanePerSide = 2
        minLanePerSide = 0

        for sl in range(5):
            path = self.configuration.get("harvested_straight_roads")
            odr = self.builder.createWithRandomLaneConfigurations(
                path,
                0,
                maxNumberOfRoadsPerJunction=maxNumberOfRoadsPerJunction,
                maxLanePerSide=maxLanePerSide,
                minLanePerSide=minLanePerSide,
                internalConnections=True,
                cp1=pyodrx.ContactPoint.end,
                internalLinkStrategy=LaneConfigurationStrategies.SPLIT_ANY)

            # xmlPath = f"output/test_createWithRandomLaneConfigurations-split-any-{maxNumberOfRoadsPerJunction}-{sl}.xodr"
            xmlPath = f"output/seed-{self.seed}-{maxNumberOfRoadsPerJunction}-way-{sl}.xodr"
            odr.write_xml(xmlPath)

            # extensions.view_road(odr,os.path.join('..',self.configuration.get("esminipath")))
            extensions.saveRoadImageFromFile(
                xmlPath, self.configuration.get("esminipath"))
            maxNumberOfRoadsPerJunction += 1

    def test_4WayJunctionsEqualAngles(self):

        maxNumberOfRoadsPerJunction = 4
        maxLanePerSide = 1
        minLanePerSide = 1

        for sl in range(5):
            path = self.configuration.get("harvested_straight_roads")
            odr = self.builder.createWithRandomLaneConfigurations(
                path,
                0,
                maxNumberOfRoadsPerJunction=maxNumberOfRoadsPerJunction,
                maxLanePerSide=maxLanePerSide,
                minLanePerSide=minLanePerSide,
                internalConnections=True,
                cp1=pyodrx.ContactPoint.end,
                internalLinkStrategy=LaneConfigurationStrategies.SPLIT_ANY,
                equalAngles=True)

            # xmlPath = f"output/test_createWithRandomLaneConfigurations-split-any-{maxNumberOfRoadsPerJunction}-{sl}.xodr"
            xmlPath = f"output/seed-{self.seed}-{maxNumberOfRoadsPerJunction}-way-eq-{sl}.xodr"
            odr.write_xml(xmlPath)

            extensions.view_road(
                odr, os.path.join('..', self.configuration.get("esminipath")))
            extensions.saveRoadImageFromFile(
                xmlPath, self.configuration.get("esminipath"))

    def test_SafeMinConnectionLengthForEqualAngle(self):

        sl = 0

        maxNumberOfRoadsPerJunction = 8
        sl += 1
        path = self.configuration.get("harvested_straight_roads")
        odr = self.builder.createWithRandomLaneConfigurations(
            path,
            0,
            maxNumberOfRoadsPerJunction=maxNumberOfRoadsPerJunction,
            maxLanePerSide=3,
            minLanePerSide=0,
            internalConnections=True,
            cp1=pyodrx.ContactPoint.end,
            internalLinkStrategy=LaneConfigurationStrategies.SPLIT_ANY,
            restrictedLanes=1,
            equalAngles=True)
        extensions.view_road(
            odr, os.path.join('..', self.configuration.get("esminipath")))
        xmlPath = f"output/test_createWithRandomLaneConfigurations-split-any-{maxNumberOfRoadsPerJunction}-{sl}.xodr"
        odr.write_xml(xmlPath)

        maxNumberOfRoadsPerJunction = 8
        sl += 1
        path = self.configuration.get("harvested_straight_roads")
        odr = self.builder.createWithRandomLaneConfigurations(
            path,
            0,
            maxNumberOfRoadsPerJunction=maxNumberOfRoadsPerJunction,
            maxLanePerSide=2,
            minLanePerSide=0,
            internalConnections=True,
            cp1=pyodrx.ContactPoint.end,
            internalLinkStrategy=LaneConfigurationStrategies.SPLIT_ANY,
            restrictedLanes=1,
            equalAngles=True)
        extensions.view_road(
            odr, os.path.join('..', self.configuration.get("esminipath")))
        xmlPath = f"output/test_createWithRandomLaneConfigurations-split-any-{maxNumberOfRoadsPerJunction}-{sl}.xodr"
        odr.write_xml(xmlPath)

        maxNumberOfRoadsPerJunction = 8
        sl += 1
        path = self.configuration.get("harvested_straight_roads")
        odr = self.builder.createWithRandomLaneConfigurations(
            path,
            0,
            maxNumberOfRoadsPerJunction=maxNumberOfRoadsPerJunction,
            maxLanePerSide=2,
            minLanePerSide=0,
            internalConnections=True,
            cp1=pyodrx.ContactPoint.end,
            internalLinkStrategy=LaneConfigurationStrategies.SPLIT_ANY,
            restrictedLanes=1,
            equalAngles=True)
        extensions.view_road(
            odr, os.path.join('..', self.configuration.get("esminipath")))
        xmlPath = f"output/test_createWithRandomLaneConfigurations-split-any-{maxNumberOfRoadsPerJunction}-{sl}.xodr"
        odr.write_xml(xmlPath)

        maxNumberOfRoadsPerJunction = 8
        sl += 1
        path = self.configuration.get("harvested_straight_roads")
        odr = self.builder.createWithRandomLaneConfigurations(
            path,
            0,
            maxNumberOfRoadsPerJunction=maxNumberOfRoadsPerJunction,
            maxLanePerSide=2,
            minLanePerSide=0,
            internalConnections=True,
            cp1=pyodrx.ContactPoint.end,
            internalLinkStrategy=LaneConfigurationStrategies.SPLIT_ANY,
            restrictedLanes=1,
            equalAngles=True)
        extensions.view_road(
            odr, os.path.join('..', self.configuration.get("esminipath")))
        xmlPath = f"output/test_createWithRandomLaneConfigurations-split-any-{maxNumberOfRoadsPerJunction}-{sl}.xodr"
        odr.write_xml(xmlPath)
Ejemplo n.º 6
0
class HDMapBuilder:

    def __init__(self, nIntersections, p=[0.2, 0.7, 0.1, 0.1], startId=0, seed=0, mapSize=(500, 500), cellSize=(100, 100), debug=True) -> None:

        self.nIntersections = nIntersections
        self.p = p # probability distribution of 3-way, 4, 5, 6
        self.intersections = {} # DirectionIntersection -> intersection
        self.placedIntersections = {}
        self.rotation = {}
        self.nextIntersectionId = startId
        
        self.seed = seed
        self.builder = SequentialJunctionBuilder(
                                                    minAngle=np.pi/6, 
                                                    maxAngle=np.pi * .75,
                                                    straightRoadLen=1, 
                                                    probLongConnection=0.3,
                                                    probMinAngle=0.1,
                                                    probRestrictedLane=0,
                                                    maxConnectionLength=30,
                                                    minConnectionLength=12,
                                                    random_seed=self.seed)

        self.moreThan4Builder = SequentialJunctionBuilder(
                                                    minAngle=np.pi/10, 
                                                    maxAngle=np.pi * .75,
                                                    straightRoadLen=1, 
                                                    probLongConnection=0.5,
                                                    probMinAngle=0.5,
                                                    probRestrictedLane=0,
                                                    maxConnectionLength=50,
                                                    minConnectionLength=20,
                                                    random_seed=self.seed)


        self.validator = IntersectionValidator()
        self.intersectionAdapter = IntersectionAdapter()
        
        self.grid = Grid(size=mapSize, cellSize=cellSize)

        self.mapBuilder = MapBuilder(self.grid, [], random_seed=40)

        self.network = None

        self.debug = debug

        self.name = "HDMapBuilder"

        self.nextRoadId = 0

        pass


    def createIntersections(self):

        print(f"{self.name}: createIntersections")
        minLanePerSide = 1
        maxLanePerSide = 2
        self.nextRoadId = 0
        for sl in range(self.nIntersections):
            print(f"{self.name}: creating {sl + 1}")
            maxNumberOfRoadsPerJunction = np.random.choice([3, 4, 5], p=[0.6, 0.375, 0.025])
            intersection = self.createValidIntersection(sl, self.nextRoadId, maxNumberOfRoadsPerJunction, minLanePerSide, maxLanePerSide)
            
            self.nextRoadId = intersection.getLastRoadId() + 100
            directionIntersection = self.intersectionAdapter.intersectionTo4DirectionIntersection(intersection)
            self.intersections[directionIntersection] = intersection

    
    def createValidIntersection(self, id, firstRoadId, maxNumberOfRoadsPerJunction, minLanePerSide, maxLanePerSide, rotate=False):

        isEqualAngle = np.random.choice([False, True], p=[0.3, 0.7])
        minConnectionLength = self.builder.minConnectionLength

        if maxNumberOfRoadsPerJunction < 5:
            intersection = self.builder.createWithRandomLaneConfigurations("", 
                                id, 
                                firstRoadId=firstRoadId,
                                maxNumberOfRoadsPerJunction=maxNumberOfRoadsPerJunction, 
                                maxLanePerSide=maxLanePerSide, 
                                minLanePerSide=minLanePerSide, 
                                internalConnections=True, 
                                cp1=pyodrx.ContactPoint.end,
                                internalLinkStrategy = LaneConfigurationStrategies.SPLIT_ANY,
                                equalAngles=isEqualAngle,
                                getAsOdr=False)
        else:
            minConnectionLength = self.moreThan4Builder.minConnectionLength
            intersection = self.moreThan4Builder.createWithRandomLaneConfigurations("", 
                                id, 
                                firstRoadId=firstRoadId,
                                maxNumberOfRoadsPerJunction=maxNumberOfRoadsPerJunction, 
                                maxLanePerSide=maxLanePerSide, 
                                minLanePerSide=minLanePerSide, 
                                internalConnections=True, 
                                cp1=pyodrx.ContactPoint.end,
                                internalLinkStrategy = LaneConfigurationStrategies.SPLIT_ANY,
                                equalAngles=isEqualAngle,
                                getAsOdr=False)

        while (self.validator.validateIncidentPoints(intersection, minConnectionLength) == False):
            intersection = self.createValidIntersection(id, self.nextRoadId, maxNumberOfRoadsPerJunction, minLanePerSide, maxLanePerSide)

        if rotate:
            # self.rotation[intersection] = np.random.uniform(0, np.pi/10)
            self.rotation[intersection] = np.random.choice([np.pi/2, np.pi, np.pi * 1.5])
            intersection.transform(startX=0, startY=0, heading=self.rotation[intersection])
            
        return intersection


    def connectIntersectionsByCellAdjacency(self):
        
        self.network = Network(self.placedIntersections)
        for cell in self.grid.cellGenerator():
            if isinstance(cell.element, DirectionIntersection):
                couldConnect = False
                DI1 = cell.element
                # We need to check right and top only as left and bottoms are traversed before.
                if DI1.top.hasRoads():
                    DI2 = self.grid.topElement(cell)
                    if DI2 is not None and isinstance(DI2, DirectionIntersection):
                        if DI2.bot.hasRoads():
                            # We have a connection
                            quad1 = DI1.top
                            quad2 = DI2.bot
                            self.connectDirectionQuadrants(DI1, DI2, quad1, quad2)
                            couldConnect = True
                if DI1.right.hasRoads():
                    DI2 = self.grid.rightElement(cell)
                    if DI2 is not None and isinstance(DI2, DirectionIntersection):
                        if DI2.left.hasRoads():
                            # We have a connection
                            quad1 = DI1.right
                            quad2 = DI2.left
                            self.connectDirectionQuadrants(DI1, DI2, quad1, quad2)
                            couldConnect = True
                # if couldConnect == False:
                #     self.network.clusters.append(set([self.placedIntersections[DI1]]))


    def connectDirectionQuadrants(self, DI1, DI2, quad1, quad2):
        intersection1 = self.placedIntersections[DI1]
        intersection2 = self.placedIntersections[DI2]
        road1 = list(quad1.roads.keys())[0]
        cp1 = list(quad1.roads.values())[0]
        road2 = list(quad2.roads.keys())[0]
        cp2 = list(quad2.roads.values())[0]

        self.network.connect(self.nextRoadId, intersection1, road1, cp1, intersection2, road2, cp2, LaneSides.BOTH)
        self.nextRoadId += 1


    
    def adjustIntersectionPositions(self):
        odrList = []
        for cell in self.grid.cellGenerator():
            if isinstance(cell.element, DirectionIntersection):
                directionIntersection = cell.element
                x, y = self.grid.getAbsCellPosition(cell)
                # introduceNoise = np.random.choice([False, True], p=[0.1, 0.9])
                introduceNoise = True
                if introduceNoise:
                    # x = np.random.uniform(x, x + (cell.size[0] / 2))
                    # y = np.random.uniform(y, y + (cell.size[1] / 2))
                    # x = x + self.grid.cellNoises[cell] * cell.size[0]
                    # y = y + self.grid.cellNoises[cell] * cell.size[1]
                    x = x + self.grid.cellNoises[cell]
                    y = y + self.grid.cellNoises[cell]

                intersection = self.intersections[directionIntersection]
                if self.debug:
                    logging.info(f"Translating {intersection.id} to ({x}, {y})")

                rotation = 0
                if intersection in self.rotation:
                    rotation = self.rotation[intersection]

                intersection.transform(startX=x, startY=y, heading=rotation)
                # ODRHelper.transform(intersection.odr, startX=x, startY=y, heading=rotation)
                self.placedIntersections[directionIntersection] = intersection

                odrList.append(intersection.odr)
        return odrList

    
    def buildMap(self, name, plot=True):

        # 1 create intersections and direction intersections.
        if self.debug:
            logging.info(f"{self.name}: Building new HDMap")
        self.createIntersections()
        self.mapBuilder.setDirectionIntersections(list(self.intersections.keys()))
        self.mapBuilder.run(self.nIntersections * 2, plot=plot)

        # now each cell in the grid has reference to the direction intersection 
        if self.debug:
            logging.info(f"{self.name}: adjustIntersectionPositions")
        odrList = self.adjustIntersectionPositions()

        self.connectIntersectionsByCellAdjacency()

        combinedOdr = ODRHelper.combine(odrList, name)
        ODRHelper.addAdjustedRoads(combinedOdr, self.network.connectionRoads)

        if self.debug:
            logging.info(f"{self.name}: buildMap: number of clusters: {len(self.network.clusters)}")
            self.network.logClusters()
        return combinedOdr
Ejemplo n.º 7
0
class JunctionHarvester:

    def __init__(self, 
                outputDir, 
                outputPrefix, 
                lastId=0, 
                minAngle = np.pi / 10, 
                maxAngle = np.pi, 
                straightRoadLen = 10,
                esminiPath = None, 
                saveImage = True):
        """The angle between two connected roads are >= self.minAngle <= self.maxAngle

        Args:
            outputDir ([type]): [description]
            outputPrefix ([type]): [description]
            lastId (int, optional): [description]. Defaults to 0.
            minAngle ([type], optional): [description]. Defaults to np.pi/10.
            maxAngle ([type], optional): [description]. Defaults to np.pi.
            straightRoadLen : used both for normal and connection roads
            esminiPath: Path to esmini to generate images for roads.
        """

        self.destinationPrefix = os.path.join(outputDir, outputPrefix)
        self.minAngle = minAngle
        self.maxAngle = maxAngle
        self.lastId = lastId
        self.straightRoadLen = straightRoadLen

        self.roadBuilder = RoadBuilder()
        self.straightRoadBuilder = StraightRoadBuilder()

        self.junctionBuilder = JunctionBuilder(self.roadBuilder)
        self.sequentialJunctionBuilder = SequentialJunctionBuilder(self.roadBuilder, self.straightRoadLen, minAngle=minAngle, maxAngle=maxAngle)

        self.junctionMerger = JunctionMerger(outputDir, outputPrefix, lastId)

        self.configuration = Configuration()

        if esminiPath is None:
            self.esminiPath = self.configuration.get("esminipath")
        else:
            self.esminiPath = esminiPath

        self.saveImage = saveImage

        if os.path.isdir(self.esminiPath) is False:
            logging.warn(f"Esmini path not found {self.esminiPath}. Will break if you try to save images using harvester.")

        pass


    

    def getOutputPath(self, fname):
        return self.destinationPrefix + fname + '.xodr'

    
    def createOdr(self, name, roads, junctions):

        return extensions.createOdr(name, roads, junctions)
        
        # odr = extensions.ExtendedOpenDrive(name)
        # for r in roads:
        #     odr.add_road(r)
        
        # for junction in junctions:
        #     odr.add_junction(junction)

        # print(f"starting adjustment. May freeze!!!!!!!!!!!!!")
        # odr.adjust_roads_and_lanes()

        # return odr


    def harvest2ways2Lanes(self, stepAngle=np.pi/20, maxTries = 100, seed=39):
        """We create junctions of two roads. Will create at least one road per angle.

        Args:
            stepAngle ([type], optional): used to generate angles between roads in conjunction with min and max angles. Defaults to np.pi/20.
            maxTries (int, optional): maximum number of junctions will be maxTries. Defaults to 1000.
            seed (int, optional): defaults to 39
        """
        np.random.seed(seed)
        # for each angle
        countCreated = 0
        roadsPerAngle = self.getRoadsPerAngle(maxTries, stepAngle)

        angleBetweenRoads = self.minAngle 
        odrObjectsPerAngle = {} # we will save the odrs keyed by angles

        while (countCreated < maxTries and angleBetweenRoads < self.maxAngle ):

            odrObjects = self.randomSome2ways2Lanes(angleBetweenRoads, roadsPerAngle)
            odrObjectsPerAngle[str(angleBetweenRoads)] = odrObjects
            countCreated += roadsPerAngle
            angleBetweenRoads += stepAngle
        
        print(f"created {countCreated} roads")

        # Save the odrs

        with(open(self.destinationPrefix + "harvested2R2LOrds.dill", "wb")) as f:
            dill.dump(odrObjectsPerAngle, f)
            print("Odr objects saved to " + self.destinationPrefix + "harvested2R2LOrds.dill" )

        pass


    def getRoadsPerAngle(self, maxTries, stepAngle):
        roadsPerAngle = round((maxTries * stepAngle)/(self.maxAngle - self.minAngle))
        if roadsPerAngle == 0:
            roadsPerAngle = 1
        return roadsPerAngle

    
    def randomSome2ways2Lanes(self, angleBetweenRoads, numberOfRoads):
        """Creates 2way junctions where the connected roads have fixed angle

        Args:
            angleBetweenRoads ([type]): The angle between the roads. The connecting road is a curve that ensures the angle is preserved.
            numberOfRoads ([type]): number of roads to be generated

        Returns:
            [type]: [description]
        """

        print( f"randomSome2ways2Lanes: creating {numberOfRoads} for angle: {math.degrees(angleBetweenRoads)}")
        odrObjects = []
        for i in range( numberOfRoads):
            odr = self.random2ways2Lanes(angleBetweenRoads)
            odrObjects.append(odr)

            # 1. save the xml file
            fname = "2R2L_" + str(round(math.degrees(angleBetweenRoads))) + "_no" + str(i)
            xmlPath = self.getOutputPath(fname)
            odr.write_xml(xmlPath)

            # 2. save image
            if self.saveImage is True:
                extensions.saveRoadImageFromFile(xmlPath, self.esminiPath)

        return odrObjects


    def random2ways2Lanes(self, angleBetweenRoads):
        # print( f"random2ways2Lanes: creating a road network for angle: {math.degrees(angleBetweenRoads)}")
        roads = []
        roads.append(pyodrx.create_straight_road(0, length=self.straightRoadLen)) # cannot reuse roads due to some references to links cannot be reinitialized with pyodrx lib.
        roads.append(self.createRandomConnectionConnectionRoad(1, angleBetweenRoads))
        roads.append(pyodrx.create_straight_road(2, length=self.straightRoadLen))

        self.link3RoadsWithMidAsJunction(roads)
        junction = self.create2RoadJunction(roads)

        self.lastId += 1

        odrName = 'R2_L2_' + str(self.lastId)
        odr = self.createOdr(odrName, roads, [junction])

        return odr


    def createRandomConnectionConnectionRoad(self, connectionRoadId, angleBetweenRoads):
        """The magic connectionRoad

        Args:
            angleBetweenRoads ([type]): The angle between the roads which this connectionRoad is suppose to connect together
            connectionRoadId ([type]): id to be assigned to the new connection road.
        """

        if angleBetweenRoads > 3.05: # when it's greater than 174 degrees, create a straight road
            return pyodrx.create_straight_road(connectionRoadId, length=self.straightRoadLen, junction=1)

        connectionRoad = self.roadBuilder.createRandomCurve(connectionRoadId, angleBetweenRoads, isJunction=True)
        return connectionRoad


    def link3RoadsWithMidAsJunction(self, roads):
        roads[0].add_successor(pyodrx.ElementType.junction,1, pyodrx.ContactPoint.start)
        self.linkInsideRoad(roads[1], 0, 2)
        roads[2].add_predecessor(pyodrx.ElementType.junction,1, pyodrx.ContactPoint.end)


    def linkInsideRoad(self, connectionRoad, predecessorId, successorId):
        connectionRoad.add_predecessor(pyodrx.ElementType.road, predecessorId, pyodrx.ContactPoint.end)
        connectionRoad.add_successor(pyodrx.ElementType.road, successorId, pyodrx.ContactPoint.start)

        pass

    def create2RoadJunction(self, roads):
        """[summary]

        Args:
            roads ([type]): 3 roads with mid as the junction type.
        """

        connection = self.connect2LaneRoads(0, 1)
        junction = pyodrx.Junction('test',1)
        junction.add_connection(connection)

        return junction

    
    def connect2LaneRoads(self, incomingRoadId, connectionRoadId,):
        """Assumes no center lane offset.

        Args:
            incomingRoadId ([type]): [description]
            connectionRoadId ([type]): [description]
        """
        connection = pyodrx.Connection(incomingRoadId, connectionRoadId, pyodrx.ContactPoint.start)
        connection.add_lanelink(-1,-1)
        return connection

        
    def harvest3WayJunctionsFrom2Ways(self, ingredientsFile, maxTries = 100, randomizeAngleSelection = True):
        """Merges 2 way junctions into 3 ways.

        Args:
            ingredientsFile ([type]): [description]
            maxTries (int, optional): [description]. Defaults to 100.
            randomizeAngleSelection (bool, optional): If True it will pick 2R roads randomly without any angle filtering. If false, it will only connect 2R roads which have different angles. Defaults to True.

        Raises:
            NotImplementedError: [description]
        """
        with(open(ingredientsFile, 'rb')) as f:
            odrDic = dill.load(f)

        selectedOdrs = None
        generatedOdrs = []
        if randomizeAngleSelection:
            odrList = []
            for angleOdrList in odrDic.values():
                odrList += angleOdrList

            numberOfOds = len(odrList)

            for _ in range(maxTries):
                try:
                    selectedOdrs = [odrList[np.random.choice(numberOfOds)], odrList[np.random.choice(numberOfOds)]]
                    newOdr = self.junctionMerger.merge2R2L(selectedOdrs)
                    generatedOdrs.append(newOdr)
                    self.lastId += 1

                except IncompatibleRoadsException:
                    continue
                
        else: 
            # TODO angle permutation for merging 2ways
            raise NotImplementedError("Angle permutation is not implemented yet")

    
        
        with(open(self.destinationPrefix + "harvested3R2LOrds.dill", "wb")) as f:
            dill.dump(generatedOdrs, f)
            print("Odr objects saved to " + self.destinationPrefix + "_harvested3R2LOrds.dill" )

        pass 


    def harvestByPainting2L(self, maxNumberOfRoadsPerJunction, triesPerRoadCount, show=False):
        """[summary]

        Args:
            maxNumberOfRoadsPerJunction ([type]): [description]
            triesPerRoadCount ([type]): number of junctions to be created for each set of roads. for 3 roads, we will try triesPerRoadCount, for 4 roads the same.
            save (bool, optional): [description]. Defaults to True.
        """

        odrs = []
        
        for numRoads in range(3, maxNumberOfRoadsPerJunction + 1):
            for _ in range(triesPerRoadCount):
                odr = self.sequentialJunctionBuilder.drawLikeAPainter2L(self.lastId, numRoads)
                self.lastId += 1

                if show:
                    extensions.view_road(odr, os.path.join('..', self.esminiPath))
                    
                xmlPath = self.getOutputPath(odr.name)
                odr.write_xml(xmlPath)
                # 2. save image
                if self.saveImage is True:
                    extensions.saveRoadImageFromFile(xmlPath, self.esminiPath)
                
                odrs.append(odr)
        
        
        
        with(open(self.destinationPrefix + "harvestedByPainting2L.dill", "wb")) as f:
            dill.dump(odrs, f)
            print("Odr objects saved to " + self.destinationPrefix + "harvestedByPainting2L.dill" )

        pass