prochitecture / blosm

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

Facade classification #8

Open vvoovv opened 3 years ago

vvoovv commented 3 years ago

I started the FacadeClassification class.

vvoovv commented 3 years ago

Several iteration passes for vector in building.polygon.getVectors() will be probably needed.

vvoovv commented 3 years ago

Use vector.next or vector.prev to access a neighbor vector.

vvoovv commented 3 years ago

It looks like a bug.

A number of shared edges (marked with the blue color) are classified as the front facades for _husumaltstadt.osm.

image

polarkernel commented 3 years ago

A number of shared edges (marked with the blue color) are classified as the front facades for husum_altstadt.osm.

A first bug fix for the larger edge at the right of the image (see image below).

Although it should be a front facade, it has a visibility of zero (see the numbers in the image). Like this, the building had no visible edges at all (all others are shared edges) and was classified using the rule for 'invisible' buildings: Find the longest edge and mark it as front edge. And this was a bug, because shared edges have to be excluded from this search. It is fixed now, but not yet committed.

However, it is only a symptom for another issue: Why is this edge not visible? I found the reason by a brute force debugging. In the image below, every way-segment, associated in visInfo to an edge that has a non-zero visibility, is marked by an arrow (magenta) pointing from the middle of the segment to its edge:

The dotted green line shows the border of the search range, currently without any margin to the way-segment. It narrowly misses this edge, while the next segment is already tilted too much, so that its search-range again misses this edge. It is an example for possible issues when not using a margin. I already pointed once to this problem when I explained the reason for the 10m margin in Discussions.

It is also interesting how the order of processing the way-segments influences these associations. If you follow the front edges from the edge I discussed above to the left, there are not all edges associated to the long way-segment. Two of them seem to be processed by the short segment at the left, before the long segment.

I continue searching for solutions (and for other bug fixes).

vvoovv commented 3 years ago

In the image below, every way-segment, associated in visInfo to an edge that has a non-zero visibility, is marked by an arrow (magenta) pointing from the middle of the segment to its edge:

Very useful visualization. Can we have it in the module _mpl.renderer.facadeclassification?

vvoovv commented 3 years ago

It is also interesting how the order of processing the way-segments influences these associations. If you follow the front edges from the edge I discussed above to the left, there are not all edges associated to the long way-segment. Two of them seem to be processed by the short segment at the left, before the long segment.

How about using the angle between the building edge and a way segment in the method VisibilityInfo.__gt__?

vvoovv commented 3 years ago

How about using the angle between the building edge and a way segment in the method VisibilityInfo.__gt__?

A way-segment with the angle to the building edge that is closer to zero would win.

vvoovv commented 3 years ago

It is an example for possible issues when not using a margin. I already pointed once to this problem when I explained the reason for the 10m margin in Discussions.

A possible solution can be using the margin, detect T-crossings and skip the buildings to the left from the base of the 'T'. Detection of T-crossings will be needed anyway for the generation of realistic streets.

polarkernel commented 3 years ago

A first bug fix for the larger edge at the right of the image (see image below).

Bug fixed and committed.

Very useful visualization. Can we have it in the module mpl.renderer.facade_classification?

Committed. Use the command line parameter --showAssoc, available for BuildingVisibilityRender and BuildingClassificationRender.

polarkernel commented 3 years ago

How about using the angle between the building edge and a way segment in the method VisibilityInfo.gt? A way-segment with the angle to the building edge that is closer to zero would win.

The association between the way-segement and the edge is currently not used (beside the check for the way-category). So this feature would not change anything. However, I will test a solution, there's nothing wrong to get more order.

A possible solution can be using the margin, detect T-crossings and skip the buildings to the left from the base of the 'T'. Detection of T-crossings will be needed anyway for the generation of realistic streets.

T-crossings are not the only issue, also dead-ends can produce such issues, as once "solved" here.

What about making the margins adaptive to the angle between the way segments, using some indicator for this angle, something like the absolute value of a dot product. For large angles, like in the above issue, the margin would be wider, smaller for small angles and zero at the ends of a way. Do you think it would be very complex to provide values for these angles (not necessarily linear) for both sides of a way-segment?

vvoovv commented 3 years ago

What about making the margins adaptive to the angle between the way segments, using some indicator for this angle, something like the absolute value of a dot product. For large angles, like in the above issue, the margin would be wider, smaller for small angles and zero at the ends of a way. Do you think it would be very complex to provide values for these angles (not necessarily linear) for both sides of a way-segment?

What if there are 3 or more way-segments sharing a node?

Did you mean the normalized absolute value of the dot product, i.e. the absolute value of the cosine between the related way-segments?

polarkernel commented 3 years ago

What if there are 3 or more way-segments sharing a node?

There can't be a perfect solution in my opinion. I would just take the linear way as defined by OSM and set the angle to zero at its ends.

Did you mean the normalized absolute value of the dot product, i.e. the absolute value of the cosine between the related way-segments?

I thought on a even simpler solution. In the bpypolyskel project I used a pseudoangle, defined as follows for a vector d (the vector from the start to the end of a way-segment):

# Adapted from https://stackoverflow.com/questions/16542042/fastest-way-to-sort-vectors-by-angle-without-actually-computing-that-angle
# Input:  d: difference vector.
# Output: a number from the range [0 .. 4] which is monotonic
#         in the angle this vector makes against the x axis.
def pseudoangle(d):
    p = d[0]/(abs(d[0])+abs(d[1])) # -1 .. 1 increasing with x
    if d[1] < 0: 
        return 3 + p  #  2 .. 4 increasing with x
    else:
        return 1 - p  #  0 .. 2 decreasing with x

Something similar could be used to compute the angle of a way-segment relative to the x-axis. The differences of this angle to the values of the prev and next of the way-segment would already be a good approximations, in my opinion.

Another very simple solution would use a zero margin at the ends of a way and a constant margin for all segments in between. Maybe this could be a starting point for experiments. At least it would solve the dead-end and T-junction problems.

vvoovv commented 3 years ago

Another very simple solution would use a zero margin at the ends of a way and a constant margin for all segments in between. Maybe this could be a starting point for experiments. At least it would solve the dead-end and T-junction problems.

I'd like to note that it's a more complex problem. Two ways may share a single node. A way may turn left or right at a T-junction. I'll add a Junction class.

image

image

vvoovv commented 3 years ago

Also I'll add the code to treat the case of a node shared by two different ways.

polarkernel commented 3 years ago

I'd like to note that it's a more complex problem. Two ways may share a single node. A way may turn left or right at a T-junction. I'll add a Junction class.

I see, my thinking was once again too easy.

The new visualization discovered more problems already on the determination of visibility. Way-segments in tunnels behave different since we introduced shared edges, as shown below. I try to fix this issue.

vvoovv commented 3 years ago

The new visualization discovered more problems already on the determination of visibility. Way-segments in tunnels behave different since we introduced shared edges, as shown below. I try to fix this issue.

Where this case is located?

The class way.Way have 2 boolean attributes: tunnel and bridge. I guess the majority of tunnels and bridges can be safely skipped from the visibility calculation. But there could be exceptions. There are even bridges serving also as buildings, for example Ponte Vecchio.

Anyway I skipped bridges and tunnels from the visibility calculations right at way.manager.WayManager.getFacadeVisibilityWays(..).

polarkernel commented 3 years ago

Way-segments in tunnels behave different since we introduced shared edges, as shown below. Where this case is located?

It is in _husumaltstadt.osm immediately at the right of the place I discussed the other problem above. However, in the meantime I found that it is not a bug. The way that goes through the tunnel there really sees a very small part of this edge and produces a visibility of 0.01.

Anyway I skipped bridges and tunnels from the visibility calculations right at way.manager.WayManager.getFacadeVisibilityWays(..).

Does that mean that we skip the requirement that passages should have front facades?

vvoovv commented 3 years ago

It is in _husumaltstadt.osm immediately at the right of the place I discussed the other problem above.

But there is a building in that place. Did you remove it for testing? image

Does that mean that we skip the requirement that passages should have front facades?

No. Only the tunnels are skipped. The ways leading to the tunnels do take part in the visibility calculation.

polarkernel commented 3 years ago

But there is a building in that place. Did you remove it for testing?

You are right. It seems that it's now me that has this magic behavior. In a recent run, the building is there again and the visibility has gone. But what could make different results using identical code? Very strange.

polarkernel commented 3 years ago

You are right. It seems that it's now me that has this magic behavior. In a recent run, the building is there again and the visibility has gone. But what could make different results using identical code? Very strange.

The reason for the magic behavior is a person: Me! I divided _husumaltstadt.osm into a couple of smaller files to ease debugging. For the above test I used accidentally a file, where this building was deleted, while the unsaved project file showed another one to me. Sorry for the confusion.

polarkernel commented 3 years ago

A way-segment with the angle to the building edge that is closer to zero would win.

I tested a first version of weighting on the comparison of two way-segments using __gt__() in VisibilityInfo, using four goals:

Visibility can immediately used as a weight. For the angle condition I used dx and dy and an equation derived from the pseudoangles:

w_angle = dx/(dx+dy)

This produces a continuous value between 0 and 1, which is not linear, but useful for comparisons:

As weight for the distance I used the distance d = abs(Y1+Y2) provided in VisibilityInfo and tried to create a weight that linearly decreases with d:

w_dist = (100.-info.distance/2.)/100. if info.distance/2. < 100. else 0.

The value 100 is the height range of the search rectangle. The distance is divided by 2 because it is a sum of two distances. Finally, the importance of the way-segment is simply weighted as a constant:

w_way = 0.5 if WayLevel[info.waySegment.way.category] == 1 else 0.0

The final weight is curently just the sum of all weights:

weight = w_angle + w_dist + w_way + visibility

Every weight could be multiplied by a constant to distribute weights differently, but this should be the goal of experiments. Here a first impression of a region around the inner harbour of Husum (left: visibility only, right: using weights):

The weighting worked best, when only applied to new way-segments that produce equal or more visibility. This solution is committed and ready for tests.

polarkernel commented 3 years ago

I fixed a bug at the insertion of crossed edges. Additionally, links to facades at dead-ends of way-segments are now plotted in blue when the command line parameter --showAssoc is applied. This new version is commited.

vvoovv commented 3 years ago

My concern is still the case of the corner facade in _manhattan01.osm. Why does the magenta arrows point to the vertices of the building edge instead of its center? Why are there 2 magenta to that building edge instead of one? Finally I'd classify that building edge the the front one.

image

image

polarkernel commented 3 years ago

My concern is still the case of the corner facade in manhattan_01.osm. Why does the magenta arrows point to the vertices of the building edge instead of its center? Why are there 2 magenta to that building edge instead of one?

The magenta arrows always point to the middle of a building edge. In this example, there are very small edges (~10cm) on both sides of the corner facade of this building, which gets visible when magnified:

Finally I'd classify that building edge the the front one.

Note that the ways on both sides of the edge in question are not perpendicular. The smallest angle, determined by dx, dy, is from the way-segment at the right of the edge and has a value of 52°, which is just above the current limit of 50°, defined as VisibilityAngle in _facadeclassification.py. Increasing this limit to 55° for example classifies this edge as front facade. We may try to increase this limit and see, whether we observe side effects.

vvoovv commented 3 years ago

The magenta arrows always point to the middle of a building edge. In this example, there are very small edges (~10cm) on both sides of the corner facade of this building, which gets visible when magnified

Sorry, I didn't notice that.

Note that the ways on both sides of the edge in question are not perpendicular. The smallest angle, determined by dx, dy, is from the way-segment at the right of the edge and has a value of 52°, which is just above the current limit of 50°, defined as VisibilityAngle in _facadeclassification.py. Increasing this limit to 55° for example classifies this edge as front facade. We may try to increase this limit and see, whether we observe side effects.

I'd like to try a different idea. I'll report about the result tomorrow.

vvoovv commented 3 years ago

I moved the angle check from _facadevisibility.py:

if VisibilityAngleFact*dx < dy: 
    _visInfo.value = 0.

to _facadeclassification.py:

        for way_level in range(1,MaxWayLevel+1): # way-level corresponds to way-categories C1, C2, C3
            for vector in building.polygon.getVectors():
                edge = vector.edge
                visInfo = edge.visInfo
                if visInfo.value and \
                        VisibilityAngleFact*visInfo.dx > visInfo.dy and \
                        WayLevel[visInfo.waySegment.way.category] == way_level:

Is it ok if I merge it to the dev branch?

polarkernel commented 3 years ago

Is it ok if I merge it to the dev branch?

I am not sure whether this will work, at least it may produce different results. Let me assume two way-segments in the loop of segments in _facadevisibility.py, applied to the same edge:

first segment: visibility 1.0, angle 80° later segment: visibility 0.9, angle 40°

In the current version, the visibility for the first segment will be set to 0. because of the angle limit and the later segment wins and gets updated. In the new version, the first segment with visibility 1. gets updated, while the later segment looses, bcause of lower visibility. But the first version is the desired one, isn't it?

vvoovv commented 3 years ago

I am not sure whether this will work, at least it may produce different results.

But we have the VisibilityInfo.weight(..) method to get the required result.

polarkernel commented 3 years ago

But we have the VisibilityInfo.weight(..) method to get the required result.

OK, when we give priority of the angle over visibility in VisibilityInfo.weight(..), it could work. Let's just try, we can always undo it.

vvoovv commented 3 years ago

Ok, I'll merge it to dev.

vvoovv commented 3 years ago

I moved all definitions to the modules _defs.facadeclassification and defs.way

vvoovv commented 3 years ago

I set VisibilityAngle = 50 again.

vvoovv commented 3 years ago

I'd expect that the building edge in the middle of the image below should be classified as the front one. The winning way segment would be the major street above the building edge rather than the service road.

image

image

polarkernel commented 3 years ago

I'd expect that the building edge in the middle of the image below should be classified as the front one. The winning way segment would be the major street above the building edge rather than the service road.

This edge gets classified already in _facadevisibility.py as deadend, as it gets crossed by the axis of the way-segment of the service road within a distance of 10m. Because the building already got front facades by ways of more importance, its class gets reset to unknown in lines 77-85. Later, it becomes a side facade.

Crossing edges can't be processed in a normal way, because their angles and distances do not fit to these rules. At this point, it is also no more possible to find the winning way-segment as you propose. Should we maybe leave it as deadend in this case?

vvoovv commented 3 years ago

Should we maybe leave it as deadend in this case?

I'd leave it as is for now.

vvoovv commented 3 years ago

The latest OSM file sirius.osm produces an error. I don't understand how it can happen. How can an edge be classified and not have waySegment?

polarkernel commented 3 years ago

The latest OSM file sirius.osm produces an error. I don't understand how it can happen. How can an edge be classified and not have waySegment?

It seems the edge was classified as the longest edge of a building by frontOfInvisibleBuilding(). In the last commit I added a condition that requests the existance of the way-segments for the two edges.

vvoovv commented 3 years ago

It seems the edge was classified as the longest edge of a building by frontOfInvisibleBuilding().

In that case only one edge is classified as the front one but in the condition

                        vector.prev.edge.cl == FacadeClass.front and \
                        vector.next.edge.cl == FacadeClass.front:

two facades must be the front ones.

polarkernel commented 3 years ago

In that case only one edge is classified as the front one but in the condition vector.prev.edge.cl == FacadeClass.front and \ vector.next.edge.cl == FacadeClass.front: two facades must be the front ones.

You are right. I found the real reason for the bug. The indent of the block at line 85 was too large. In the first run of the loop for the way-categories, the winner segment of the edge with the id 5064 had a lower category and was skipped. Then, in the block mentioned above, the segment with id 5062 was the longest and therefore selected as front. When the way-category loop did its second run with a less important way category, the segment 5064 was ordinarily set as front.

The block starting at line 85 required less indentation to be exectuted only once at the end of the function. This is fixed now and committed.

vvoovv commented 3 years ago

I successfully tested the following processing pipeline on the OSM file _feature_detection/tula_bay_windows01.osm: Feature detection -> Feature removal -> Visibility Calculation ->Facade Classification

I encountered a couple of problems with the service roads. Note the marked footprint edge with the id equal to 730.

image

(1) The winning way-segment is perpendicular to the the building edge. Shouldn't the way-segment parallel to the building edge be the winning one?

(2) The marked footprint edge is classified as the front one with the winning way-segment representing a service road. Actually it's should be classified as the back one. My proposal is to consider that a building edge with a service winning way-segment has no winning edge at all if at there is at least one building edge that is

  1. classified as the front one
  2. has a winning way-segment other than service.
vvoovv commented 3 years ago

I successfully tested the following processing pipeline on the OSM file _feature_detection/tula_bay_windows01.osm: Feature detection -> Feature removal -> Visibility Calculation ->Facade Classification

Here is how to reproduce the result:

python.exe -m script.command_line --buildings --highways --detectFeatures --simplifyPolygons --classification --setupScript setup\mpl_facade_visibility.py --osmFilepath ..\osm_extracts\feature_detection\tula_bay_windows_01.osm
polarkernel commented 3 years ago

(1) The winning way-segment is perpendicular to the the building edge. Shouldn't the way-segment parallel to the building edge be the winning one?

This edge gets classified as deadend already in _facadevisibility.py. As deadend, it gets excluded from the standard classicfication process classifyFrontFacades() in _facadeclassification.py and, as it belongs to the group CrossedFacades, it gets classified as front in without further considerations (line 25). This explains why the perpendicular way-segment is associated to this edge.

(2) The marked footprint edge is classified as the front one with the winning way-segment representing a service road. Actually it's should be classified as the back one.

Without the classification as deadend, this edge would not be classified as front facade. I propose to remove the exclusion from classifyFrontFacades() for CrossedFacades edges. These should then only get front edges, when classified as such in classifyFrontFacades(), or when their way-segment level is at least as high as an already detected front egde of the building, if any, or finally when no other front edge was found for the building.

I try to implement and test this approach.

polarkernel commented 3 years ago

I try to implement and test this approach.

Solution tested and committed. Seams to work.

vvoovv commented 3 years ago

Yes, now it outputs the expected result.

vvoovv commented 2 years ago

I'd like to share the initial results of integration of the facade classification processing pipeline with Blender.

The green color is used for front facades.

There are very narrow side facades in the screenshots marked with the yellow color.

The blue color is used for building parts. Building parts will somehow inherit the class of the footprint of the whole building.

The gray color is used for shared facades.

I was afraid how the Blender's KD-tree would behave. Likely it seemingly delivers the same results as scipy's KD-tree.

image

image

polarkernel commented 2 years ago

I'd like to share the initial results of integration of the facade classification processing pipeline with Blender.

Looks fine. Interesting, all the theoretical considerations we made now become realistically visible.

I was afraid how the Blender's KD-tree would behave.

You have nerves of steel if you have tried this for the first time only now! Glad to hear that it works.

vvoovv commented 2 years ago

I am currently writing the code to inherit the facade class for a building part from the one for the whole building footprint.

vvoovv commented 2 years ago

I've discovered an issue with passages. File _facade_visibility/manhattan01.osm. Vector ID is 42.

I think the facade should be classified as a front one.

image

image

polarkernel commented 2 years ago

I think the facade should be classified as a front one.

I am no more sure, in which repo did you observe this?

vvoovv commented 2 years ago

In the branch dev.