prochitecture / blosm

GNU General Public License v3.0
11 stars 3 forks source link

Enabling Bundles and intersections with Bundles, produced in StreetGenerator #104

Open polarkernel opened 1 month ago

polarkernel commented 1 month ago

Let me use an example that is not as clean as those created in Berlin, Karl-Marx-Allee, to illustrate some initial issues to be discussed (from _/osm_extracts/facade_visibility/moscow_leningradskiprospekt.osm, slightly rotated):

In green, there is a bundle of way-sections, classified as 'parallel' by the current algorithm (created in the method createParallelSections). The ways in green, that are perpendicular to this bundle, do not belong to it, that's just an effect of the random color generator. In my opinion, the selected sections reasonably belong to this bundle.

Programmatically, a bundle of ways, classified as 'parallel', is collected as a set of way-sections. The connections between them have to be reconstructed.

Some initial observations:

vvoovv commented 1 month ago

I added footway, cycleway and path to ExcludedWayTags:

ExcludedWayTags = ['steps', 'footway', 'cycleway', 'path']

Is it the right place for excluding some tags used in generating Bundles?

Now it looks better:

image

Why is there a black segment?

image

I hope to post more thoughts later.

polarkernel commented 1 month ago

Is it the right place for excluding some tags used in generating Bundles?

No. There you exclude them from the network completely. A restriction is made in the function canPair (/defs/way_cluster_parrams), which controls possible pairing combinations of way categories. Another parameter is searchDist (same file), which determines the search distance for parallelism, different for different categories.

If you like to experiment with exclusions of categories from the bundles, you may insert the following instruction at line 613 (current empty):

    if section.category in ['steps', 'footway', 'cycleway', 'path']:
        continue

However, I suggest that we do not change these criteria for the time being. I spent a lot of time fine-tuning them to get the best possible results. No matter what you do, you will always find another instance in some scene where it doesn't work. You will always find one of the observations I presented above, or another unexpected case. At least, that's my experience.

By the way, parts of the algorithm are explained here.

Why is there a black segment?

There is a length limit in line 645. But to be honest, I don't remember the reason for that. Maybe to avoid short segments within intersections.

vvoovv commented 1 month ago

I suggest that we consider for now only bundles with constant number of ways. For example a Bundle with 2 ways start at an intersection. The same 2 ways enter another intersection where the Bundle ends.

Also I suggest that some intersections with the predefined conditions do not break a Street into 2 Streets. Here are some examples of those conditions:

vvoovv commented 1 month ago

Also I suggest that some intersections with the predefined conditions do not break a Street into 2 Streets.

Those intersections are local to the Street and can be collected in a list as a property of the Street. They are not included to the global list of intersections.

vvoovv commented 1 month ago

I excluded the following categories in the line 614 of the method createParallelSections(..) of the module bloms.action.generate_streets.

  if section.category in ('steps', 'footway', 'cycleway', 'path', 'service'):
      continue
polarkernel commented 1 month ago

I committed a debug version to help understand the current state of development and discuss our proposals. This feature can be enabled in line 722 of _/action/generatestreets.py, by setting inBundles to True. When run in script mode, it will plot a bundle of parallel sections, as found by createParallelSections(), one after the other onto the road network (unfortunately slow). Note, that the order of these sections is not yet sorted, they are just stored as a set of Section objects. The title of the plots shows the identification number of such a set, to facilitate identification during our discussions. The intersections between and at the ends of these sections are shown as green circles.

It takes some patience to locate the interesting cases, but I think it's worth it. Scenes with a lot of interesting cases are:

_/osm_extracts/facade_visibility/bratislava_oldtown.osm _/osm_extracts/streets/milano01.osm _/osm_extracts/facade_visibility/moscow_leningradskiprospekt.osm

vvoovv commented 1 month ago

Thanks. BTW, _/osm_extracts/facade_visibility/bratislava_oldtown.osm is mentioned twice in your previous message.

vvoovv commented 1 month ago

What do you think about my proposal of ignoring some intersections that satisfy the predefined conditions?

polarkernel commented 1 month ago

BTW, /osm_extracts/facade_visibility/bratislava_old_town.osm is mentioned twice in your previous message.

Uuups, should have been _/osm_extracts/facade_visibility/moscow_leningradskiprospekt.osm. I'll edit it in the original post.

polarkernel commented 1 month ago

What do you think about my proposal of ignoring some intersections that satisfy the predefined conditions?

-- I suggest that we consider for now only bundles with constant number of ways.

I agree.

-- Also I suggest that some intersections with the predefined conditions do not break a Street into 2 Streets.

I see the idea. This would definitively extend the bundle's lengths.

-- Those intersections are local to the Street and can be collected in a list as a property of the Street. They are not included to the global list of intersections.

OK, for the bundle, but what happens with the intersections of these streets, when they leave the bundle, so that the intersection at one end is in this global list, while the other end is outside the bundle? What, if both ends are inside the bundle? Remove those ways? I am not sure if I understand your suggestion.

I am currently studying the solutions I already found a long time ago, where the code is still in the class StreetGenerator. In the (currently commented) methods createLongClusterWays, createClippedClusterEnds and cleanCoveredWays, it took a total of about 1200 lines of code to find some heuristics, that at least worked in many cases. We are now approaching a very complicated part of the project.

What is new, however, are the possibilities opened up by the use of geometric nodes. So solving your requirements may lead us to other, as yet undiscovered solutions.

vvoovv commented 1 month ago

I am not sure if I understand your suggestion.

I suggest dividing our intersections into global and local ones. The global intersections are those in the existing data structure for the intersections. The global intersections define a street configuration on the ground. Typically a street configuration with the global intersections is very rarely changed on the ground.

On the contrary, the local intersections do not change the street configuration on the ground and can be easily created on the ground. For example, a driveway to a new house, or a new pedestrian crossing in the middle of a street.

A driveway (blue) on the image below does not change the street configuration (red)

image

This would definitively extend the bundle's lengths.

It would also extend the length of ordinary streets (non-bundles).

OK, for the bundle, but what happens with the intersections of these streets, when they leave the bundle, so that the intersection at one end is in this global list, while the other end is outside the bundle? What, if both ends are inside the bundle? Remove those ways?

I don't see any problems here.

polarkernel commented 1 month ago

My problem may become more visible if I illustrate the situation in terms of structures in the way network of the scene. As a simple example, the following part of this network could exist:

The red points are instances of Intersection and form the nodes of the network, while the lines between them are instances of Street and form the edges of the network. The streets can have different categories, classified here as major or minor, according to your definitions above. Now let our algorithm decide that the major streets are parallel and can be packed as a Bundle:

The bundle is a kind of list of all roads, and the internal intersections (circles) are collected in another list of the bundle. The geometric node algorithm will construct cluster intersections from the ends of the bundle.

But what about the minor roads? The network structure can't be used anymore. The nodes and edges used for the bundle must be removed. But what shall be done with the end of the left minor road? Will it just be provided as an instance of Street with src or dst set to None? Should the minor street on the right be removed?

vvoovv commented 1 month ago

But what shall be done with the end of the left minor road? Will it just be provided as an instance of Street with src or dst set to None?

The intersection of the minor road and the upper Street in the Bundle will be included in the local list of intersections (or another appropriate data structure) of the upper Street in the Bundle. The intersection won't be available in global data structure of intersections.

The upper Street should be compose of 3 Sections since the sections are separated by the local intersections.

It will be provided as an instance of Street.

Should the minor street on the right be removed?

Both ends of this minor street constitute local intersections in the same Bundle. So this minor street is also local to the Bundle. A Bundle can have a list of connections between its streets. So a possible solution would be to include the Street into list of local connections in the Bundle and not include the Street in global data structure of Streets.

polarkernel commented 1 month ago

Maybe, my understanding now comes closer to your ideas. Let me explain, what I got until now. Starting point is the current state:

We have Street instances, stored in manager.waymap, then Intersection instances, stored in the list manager.intersections, and finally SideLane and SymLane instances, stored in the lists manager.transitionSideLanes and manager.transitionSymLanes. Let me call these mainItems.

Most of these mainItems will remain as they are, only a few items will need to be collected into a Bundle using the createParallelSections() method (and more code, not yet developed).

As I understand it, the contents of such a Bundle are:

All these items are removed from the mainItems.

The upper Street should be compose of 3 Sections since the sections are separated by the local intersections.

These 3 streets were meant to illustrate the remaining mainItems. A problem is only the street, that points down and has now lost its connection (tail or head) to the connector of the intersection. This has now become a minor intersection of the bundle and will no longer belong to the list manager.intersections.

vvoovv commented 1 month ago

First a note. All these concepts also apply to single Streets (non-Bundles).


We have Street instances ...

The Bundle is composed of 2 Streets. The upper Street is composed of 3 Sections separated by the minor intersections. The lower Street is composed of 2 Sections separated by the minor intersections.

Only a few items will need to be collected into a Bundle

I think they should be in a related Street:

  1. in a linked list of Sections, minor intersections and transitions
  2. in a Python list minorIntersections

The end links tail and head of these instances still point to the instances of IntConnector of the intersections, contained in the bundle.

Shouldn't the tail and head point to the Bundle Intersection?

A local list majorIntersections of instances of Intersection (red). These are the intersections at the ends of the bundle.

Currently a Street does not have a local list majorIntersections. I am not sure if it will be need for Bundles.

A local list minorIntersections of instances of Intersection (circles). These are the intermediate intersections between major streets.

As noted above, Minor Intersections belong to a Street rather than to a Bundle. The Minor Intersections are inserted into the linked list of a Street.

A problem is only the street, that points down and has now lost its connection (tail or head) to the connector of the intersection.

I don't understand, why it is to lose its connection to the intersection. A Minor Intersection should have the same data structure as the Major one.

polarkernel commented 1 month ago

First a note. All these concepts also apply to single Streets (non-Bundles).

Aha. So your idea is to first concatenate instances of Section to an instance of Street to make it as long as possible, and then construct bundles. So, minor intersections can only consist of two major sections and one (or more?) minor sections (of the categories footway, cycleway or the combination of the tags highway=service and service=driveway).

The class Street must be adapted to contain a list of these minor intersections (Python list minorIntersections). Within the Street, the links between the sections and the (minor) intersections can in principle remain. However, they are thought to be links between Streets and not Sections (I don't know yet what effect this will have):

Shouldn't the tail and head point to the Bundle Intersection?

That's another open question. Not all bundles end in a (clustered) intersection. An example is the South-East end of the _moscow_leningradskiprospekt (another interesting task for geometric nodes):

Currently a Street does not have a local list majorIntersections. I am not sure if it will be need for Bundles.

I don't know either. The only case I can see where it might make sense are intersections of Streets that are completely inside the Bundle:.

I don't understand, why it is to lose its connection to the intersection. A Minor Intersection should have the same data structure as the Major one.

You are right. I was too entrenched in my network structure (graph).

polarkernel commented 1 month ago

I committed a version that should help to illustrate the discussion on real scenes. Running as script, a first plot (before the plot with the parallel ways) shows the classification, as proposed:

The minor sections (of the categories footway, cycleway or the combination of the tags highway=service and service=driveway) are shown in blue, and the remaining major sections in red. Minor intersections (those with two major sections and one or more minor sections) are marked with a red cross. Major intersections (those with more than two major sections) are shown as red dots. The remaining intersections are marked with a small cyan dot.

Collecting minor sections into long Streets across intersections would eventually be possible. But then, their directions would need to be matched.

polarkernel commented 1 month ago

I wonder what the GN algorithm requires to construct a minor intersection. Given are the position of the intersection and the two major sections of the Street. The minor sections can leave the long Street, that is concatenated from the major sections, to the left or to the right. Could it be a solution, to have one list of minor sections on the right, in the order they leave, and another list of those, that leave on the left? For example, a new class MinorIntersection could be embedded like a SymLane into the double-linked list of Sections:

This MinorIntersection could be very similar to the Intersection we introduced earlier. Instead of a circular linked list, two linear linked lists would be created, held by toRight and toLeft. Additionally, for the integration into Street, the links pred and succ are added (here more detailed, but not all subtleties drawn):

vvoovv commented 1 month ago

Aha. So your idea is to first concatenate instances of Section to an instance of Street to make it as long as possible, and then construct bundles. So, minor intersections can only consist of two major sections and one (or more?) minor sections (of the categories footway, cycleway or the combination of the tags highway=service and service=driveway).

Yes. I suggest using the tag highway=service without the tag service=driveway, since people often forget to include the supplementary tag service=driveway

However, they are thought to be links between Streets and not Sections (I don't know yet what effect this will have):

Sorry, I can't figure out what's new in the scheme.

Not all bundles end in a (clustered) intersection.

If a Bundle is not connected to an Intersection, lets the attributes succ or pred of its Streets to None for now.

vvoovv commented 1 month ago

Collecting minor sections into long Streets across intersections would eventually be possible. But then, their directions would need to be matched.

Both major and minor Sections with minor Intersections should be collected into longer Streets. Are there examples of major Sections which direction do not match?

polarkernel commented 1 month ago

Both major and minor Sections with minor Intersections should be collected into longer Streets. Are there examples of major Sections which direction do not match?

I don't think so. What I meant was the concatenation of minor sections into Streets, using minor intersections. For example, the sections marked with a green sideline in the image below could be concatenated, because they have almost the same direction. The sections marked with a yellow sideline, on the other hand, have quite sharp angles and should not, in my opinion, be concatenated.

vvoovv commented 1 month ago

For example, a new class MinorIntersection could be embedded like a SymLane into the double-linked list of Sections:

I really like your proposal!

vvoovv commented 1 month ago

What I meant was the concatenation of minor sections into Streets, using minor intersections.

For now, I suggest concatenating the major sections and NOT concatenating the minor sections.

polarkernel commented 1 month ago

I really like your proposal!

Fine! If you agree, then I will start developing this part so that we can test the effect.

vvoovv commented 1 month ago

Fine! If you agree, then I will start developing this part so that we can test the effect.

I certainly agree. I will continue to work on Geometry Nodes setups for all those street elements.

polarkernel commented 1 month ago

I committed the version with the new class MinorIntersection. Given an instance of Street, running from left to right on the following image, an instance of MinorIntersection is integrated into the double-linked list by succ and pred, like the other items, intended to exist in a Street:

The linear linked lists of connectors are held by the attribute leftHead of MinorIntersection, for the minor streets to the left of the street, and rightHead for those on the right. The references of the tails (leftTail and rightTail) only increase the speed of appending these connectors. The connectors are of the IntConnector class, identical to those in normal Intersection instances. The attribute location (not drawn in this image) holds the position of the intersection.

The static method iterate_from of MinorIntersection returns an iterator for the linear lists. For example, the left list can be traversed by

            for conn in MinorIntersection.iterate_from(item.leftHead):
                 ...
polarkernel commented 1 month ago

After years of experiments, the code in the StreetGenerator class became a confusing mess. Many of the methods in it are no longer needed (including a number of imported classes). I wonder if I should do a thorough cleanup to make the whole thing more manageable again. I would just need to keep the current code in a safe place in case one or the other idea comes back later.

What do you think?

vvoovv commented 1 month ago

What do you think?

Yes, the code cleanup is desirable. A folder legacy can be used for the old code.

polarkernel commented 1 month ago

Yes, the code cleanup is desirable. A folder legacy can be used for the old code.

OK. I will start by commenting out unused code, so don't wonder about a series of commits. The functionality should always remain the same.

polarkernel commented 1 month ago

The cleaning of StreetGenerator and some files around it is done. Its length has been reduced from 2578 to 585 lines. Since the intersection areas can now be calculated by GNs, I removed the attribute area and its computation from Intersection and index from IntConnector. If they are needed again later, they can easily be reintegrated.

I will now rewrite the method createParallelSections to createParallelStreets, and try to find the now longer parallel streets instead of sections. This as a first step towards Bundles. At the same time, we can continue our discussion here and try to find rules for them.

polarkernel commented 1 month ago

There is something wrong with the solution with minor intersections. Several overlapping streets are built. I will notify you, when I fixed the bug.

vvoovv commented 3 weeks ago

I'd like to discuss data structures for Bundles.

manager.iterStreets() can also return an instance of the class Bundle. To distinguish a Bundle from a Street, both classes can have an attribute isBundle.

Bundle.pred and Bundle.succ point to IntConnectors or None. IntConnector.item points to the Bundle.

A Bundle has lists streetsHead and streetsTail. The former is used to store Streets that are present in Bundle's head, the latter is used to store Streets that are present in Bundle' tail. Due to possible merges and splits, the content of streetsHead and streetsTail may differ. To support the merges and splits of the Streets, an additional class SplitMerge may be needed.

A Street that is a part of a Bundle also has a double linked list of Sections, minor Intersections, SideLanes, etc.

Street.pred and Street.succ can be set to Bundle's IntConnector for consistency.

polarkernel commented 3 weeks ago

I'd like to discuss data structures for Bundles.

Thanks, this is a good start to this subject.

manager.iterStreets() can also return an instance of the class Bundle. To distinguish a Bundle from a Street, both classes can have an attribute isBundle.

Shouldn't we use isinstance(street,Bundle) to save the space used by isBundle?

Bundle.pred and Bundle.succ point to IntConnectors or None. IntConnector.item points to the Bundle.

OK. A Bundle cannot have a direction, so the assignment of pred and succ to its ends will be arbitrary.

A Bundle has lists streetsHead and streetsTail. The former is used to store Streets that are present in Bundle's head, the latter is used to store Streets that are present in Bundle' tail.

Again, a Bundle has no direction, so the terms head and tail may be confusing. But I know of no better term. Will it be enough to refer to instances of Street by these lists, or is it necessary to get the correct end of them?

To support the merges and splits of the Streets, an additional class SplitMerge may be needed.

What is your idea for merges and splits? Should the former Intersections, that are now inside the Bundle, become SplitMerges?

A Street that is a part of a Bundle also has a double linked list of Sections, minor Intersections, SideLanes, etc.

OK. So it stays what it currently is.

Street.pred and Street.succ can be set to Bundle's IntConnector for consistency.

I am not sure what you mean. Do you mean the streets inside the bundle, and are these the IntConnectors pointed to by Bundle.pred and Bundle.succ?

vvoovv commented 3 weeks ago

Shouldn't we use isinstance(street,Bundle) to save the space used by isBundle?

Yes, let's use it.

Again, a Bundle has no direction, so the terms head and tail may be confusing. But I know of no better term. Will it be enough to refer to instances of Street by these lists, or is it necessary to get the correct end of them?

The order of a Bundle is chosen arbitrary. These lists should be enough to trace the Streets of a Bundle down.

What is your idea for merges and splits? Should the former Intersections, that are now inside the Bundle, become SplitMerges?

A Split or a Merge in a Bundle happens if Streets in Bundle are changed (i.e. split or merged). This concept can be implemented later.

I am not sure what you mean. Do you mean the streets inside the bundle, and are these the IntConnectors pointed to by Bundle.pred and Bundle.succ?

Yes,

polarkernel commented 2 weeks ago

Before I start any coding, I would like to continue the discussion with examples, first simple ones and then, one by one, more complicated ones. First, I would like to discuss the simplest case of a bundle intersection, such as the one between Karl-Marx-Allee and Friedenstraße in Berlin:

Are the following assumptions correct?

vvoovv commented 2 weeks ago

Yes, it is correct.

I can't really comment on the location attribute since I don't deal with it.

I don't think that manager.minorIntersections will be needed at all. But let's keep it for now with all those minor intersections.

For now let's not create a list for the Streets that are not present in streetsHead or streetTail.

polarkernel commented 2 weeks ago

For now let's not create a list for the Streets that are not present in streetsHead or streetTail.

Shall we leave them in the street container or remove them? Here is another example (_/osm_extracts/streets/milano01.osm):






My next example: An intersection of two bundles that do not intersect perpendicularly (NE of _/osm_extracts/streets/milano01.osm):

The image shows the result of the algorithm in createParallelStreets(). The issue is given by the major intersection number 1, highlighted by the green arrow.

My suggestions:

vvoovv commented 2 weeks ago

For now let's not create a list for the Streets that are not present in streetsHead or streetTail.

Shall we leave them in the street container or remove them?

I've got another proposal.

We can have a dedicated iterator manager.iterBundles() for the Bundles. A Street that is a part of a Bundle, will have an attribute bundle pointing to the related Bundle. Otherwise the attribute bundle is set to None. There is no need to exclude Streets that belong to Bundles, from the street container, since those Streets can be easily filtered out.

vvoovv commented 2 weeks ago

My suggestions:

I agree. Will some Streets on the right be a part of that Intersection?

polarkernel commented 2 weeks ago

We can have a dedicated iterator manager.iterBundles() for the Bundles. A Street that is a part of a Bundle, will have an attribute bundle pointing to the related Bundle. Otherwise the attribute bundle is set to None. There is no need to exclude Streets that belong to Bundles, from the street container, since those Streets can be easily filtered out.

I like this suggestion. For an easy identification of Streets and Bundles, I will replace the container lists by dictionaries, where the keys are the IDs of their instances.

I agree. Will some Streets on the right be a part of that Intersection?

Yes. My idea is to provide an intersection with six IntConnectors, two that connect to the Bundles, and four, that connect to the major streets that leave the intersections 2 and 3 to the right. The circular double-linked list of the intersection, which contains all IntConnectors, orders them all counter-clockwise, as already used for Streets.

I would apply this idea even if only one Bundle ends at an intersection with some Streets. Only then, when a Bundle ends without any continuation, its succ or pred should be None.

polarkernel commented 2 weeks ago

A next example of a Bundle (from _/osm_extracts/streets/rotterdam01.osm):

The blue Bundle ends on the left at the intersection with the red Bundle, and on the right at an example, where succ or pred should be None (scene border). The lower street is not a long street, it consists of a sequence of three instances of Street, interrupted by the major intersections with the major Streets from below (gray).

The problems if we treat this as one Bundle:

The problems if we split the Bundle into three parts (Bundles) at these intersections:

vvoovv commented 2 weeks ago
  • How to store the instance of Street in the middle between these intersections?

I suggest making a long Street for this case similar to the minor Intersections.

  • Would the GNs be able to handle the intersections with the gray streets, just like they do with the minor intersections, where a minor street leaves the bundle?

I hope so.

polarkernel commented 2 weeks ago

I suggest making a long Street for this case similar to the minor Intersections.

OK. In this case, the major intersection must become a minor intersection. I see no problems.

Just to be clear, two different observations (both from _/facade_visibility/berlin_karl_marxallee.osm). First the intersection Karl-Marx-Allee - Koppenstraße:

The magenta and brown bundles are combined into one and the two major intersections become minor ones, each integrated into a long Street. The intersection Karl-Marx-Allee - Andreasstraße is different, because there is a connection between the intersections:

Here, the brown and the yellow bundles are each a separate bundle. The intersection becomes a major Intersection, where two IntConnectors connect to these bundles and the other two connect to the major streets that leave to the north and the south.

polarkernel commented 2 weeks ago

A next (more difficult) example to discuss. Sometimes, streets are parallel for some time and then diverge, either by widening or by narrowing (both examples from _/facade_visibility/berlin_karl_marxallee.osm, one rotated):

I assume that GNs are not able to handle this. The bundle must split between the parallel and the divergent part. Often, there is no natural intersection, where this can be done. I suggest ending the bundle streets at this point and continuing them with normal streets. An intersection is constructed, which connects with its IntConnectors to the bundle and to the remaining diverging streets.

Another possible solution would be to dispense with these bundles by simply leaving the roads as they are.

vvoovv commented 2 weeks ago

I suggest ending the bundle streets at this point and continuing them with normal streets.

A Street that is a part of a Bundle is rendered similarly to a separate Street. The main reason to have Bundles is to not generate sidewalks between the Streets of a Bundle and generate a separator between those Streets.

So there is no need for that split.

polarkernel commented 2 weeks ago

So there is no need for that split.

OK. I think the most important questions have been answered. I will now make a first attempt to code the results and see what else needs to be discussed.

polarkernel commented 2 weeks ago

Digging into the code is sometimes good. I fixed some bugs and committed the fixes. Please update.

polarkernel commented 2 weeks ago

A Bundle has lists streetsHead and streetsTail. The former is used to store Streets that are present in Bundle's head, the latter is used to store Streets that are present in Bundle' tail. Due to possible merges and splits, the content of streetsHead and streetsTail may differ.

Is it necessary for the streets in these lists to be in a certain order (e.g. from left to right)?

vvoovv commented 2 weeks ago

Is it necessary for the streets in these lists to be in a certain order (e.g. from left to right)?

Yes. I suggest setting from left to right if look along the given direction of a Bundle.

polarkernel commented 1 week ago

A next step of the development towards Bundles is completed. The bundles for the scene _berlin_karl_marxallee.osm are constructed correctly. As in previous development steps, I try to find solutions for simple scenes first and improve them later for more complicated ones. Note, that currently only the Bundles are constructed and not yet their intersections.

After finding parallel groups of streets, as already known, the code includes the following steps

Here is an illustration of a resulting Bundle:

The blue lines are the streets of the Bundle. The red circles on the left, with an 'H' in them, are the streetsHead items, at the arbitrary start of the bundle. They are numbered from left to right relative to the direction of the bundle. Their counterparts at the other end (blue circles) show the streetsTail items. They are also numbered from left to right. All the red lines are the inner streets. They can be found by their bundle attribute, which points to the Bundle instance.

If you like to see more results, uncomment the two lines 80 and 81 in _generatestreets.py and change line 389 from

        doDebug = False and self.app.type == AppType.commandLine

to

        doDebug = True and self.app.type == AppType.commandLine