3D rendering of complex roof geometry

Hey guys,
To continue a discussion in a separate thread started in:

As a fallback the roof:levels could be used from the building-outline in a similar way Urban Eye 3D is doing it at the moment. I could also imagine to add a roof:height or roof:level to the roof:ridge=yes way or even to the nodes of that way, if you think of an inclined roof.

I would think of something like:
The outline would pass the roof height to all roof:ridge within the building, but gets overwritten by values tagged on the ridge-way. Those values gets inherited to the nodes of the ridge, but get overwritten by values on the node itself.

I assumed there is a tagging for roof height or levels. We need a formular to be used for each node of the ridge ways. Either in meters or percent is ok.

Maybe I don’t get your point. If someone would add roof:height=3 or roof:level=1 (which I assume somehow gets translated into roof:height=*) to either building=* or roof:ridge=yes or its nodes would give you the height from the top of the “building” to the top of the roof(-part).

Since it’s somehow manually possible to get @Zkir Urban Eye viewer to display it properly, I would think the code itself is available, what’s missing is the generation of the subareas, transfer the data and derive the direction.

1 Like

All nodes on the footprint are at 0%? No, some are at 100%. How to know which is which?

Not all other nodes are at 100% of the given roof height. How would you detect them and calculate this one?

I added that building to OSM, so it’s easier to refer things (without the building:parts).

Isn’t code existing for that? Since it’s getting rendered somehow reasonable. If that fallback is not what the mapper wants, he would be able to add a specific roof:height (or ridge:height??) to the ridge and from the way it gets transferred to the height of Node: 13116467700 | OpenStreetMap and Node: 13116467697 | OpenStreetMap. If both nodes would be on different heights, the mapper would add the height to the node itself.

In summary, somehow the fallback can be handled already and in case that is unreasonable, the mapper would need to explicitly define the height of the ridge, so there should be no formula be needed.

What I figured, it might not be a 100% solution. Like for a dormer might need further information. Please read building-outline also as a potential building:part. Like the house (left) and the garage (right) would need to be split in two building:part.

Maybe to make it more clear:

The algorithm would need to split the building into those colored subareas, where the motorway, primary and trunk are the (split at each intersection with an roof:edge) ridge lines.

Transfer the relevant roof-data down to those subareas.
I would think each of those sub areas has one edge as roof:ridge=yes or if not a node belonging to a ridge-way.
In case of an edge take this edge, calculate the heading, get the perpendicular direction of that heading, which is facing into the subarea. In case of the blue subarea, the heading of the primary is ~357°, so the direction is ~267°.
In case of a ridge-node I would go with the opposite edge of the triangle and add +180° to get the direction. But there might be also other ways.

It would work similar for the case the ridge has a specific height. Lets say the trunk-line would have height = 2 and the motorway and primary would have height=3. So blue, green, purple, white and yellow subarea would get a roof:height=3 and red and black subarea a ridge:height=2.

Not sure though for cases where the ridge is inclined.

1 Like

Maybe I’m wrong, but that preprocessing algorithm doesn’t sounds that complicated and will get Urban Eye (or others) to a way they can render 90% of those irregular shaped buildings.

Let me think of a solution for dormer or other roof styles where the roof:edge is not ending at the outline or lower ridge.

Edit:
dormer would work in a similar manner:

Though it needs some way to describe the dormer outline. I used roof:dormer=yes for this and a roof:ridge=yes to limit it on the upper side. A hipped dormer you would get by adding a roof:ridge=yes along that ridge line.

osm xml
<?xml version='1.0' encoding='UTF-8'?>
<osm version='0.6' generator='JOSM'>
  <node id='-25386' action='modify' visible='true' lat='42.5218351291' lon='-83.20780616228' />
  <node id='-25387' action='modify' visible='true' lat='42.52183771272' lon='-83.20766147326' />
  <node id='-25388' action='modify' visible='true' lat='42.52175865521' lon='-83.20765887443' />
  <node id='-25389' action='modify' visible='true' lat='42.52175607159' lon='-83.20780356345' />
  <node id='-25390' action='modify' visible='true' lat='42.5217953831' lon='-83.20780485572' />
  <node id='-25391' action='modify' visible='true' lat='42.52179796671' lon='-83.2076601667' />
  <node id='-25394' action='modify' visible='true' lat='42.52183134223' lon='-83.2077861476' />
  <node id='-25396' action='modify' visible='true' lat='42.52183229876' lon='-83.20773399808' />
  <node id='-25404' action='modify' visible='true' lat='42.5218017779' lon='-83.2077851493' />
  <node id='-25405' action='modify' visible='true' lat='42.52180273443' lon='-83.20773299979' />
  <way id='-580' action='modify' visible='true'>
    <nd ref='-25386' />
    <nd ref='-25387' />
    <nd ref='-25391' />
    <nd ref='-25388' />
    <nd ref='-25389' />
    <nd ref='-25390' />
    <nd ref='-25386' />
    <tag k='building' v='detached' />
  </way>
  <way id='-582' action='modify' visible='true'>
    <nd ref='-25390' />
    <nd ref='-25391' />
  </way>
  <way id='-624' action='modify' visible='true'>
    <nd ref='-25404' />
    <nd ref='-25405' />
    <tag k='roof:height' v='1' />
    <tag k='roof:ridge' v='yes' />
  </way>
  <way id='-650' action='modify' visible='true'>
    <nd ref='-25405' />
    <nd ref='-25396' />
    <nd ref='-25394' />
    <nd ref='-25404' />
    <tag k='height' v='5.5' />
    <tag k='roof:dormer' v='yes' />
  </way>
  <way id='-678' action='modify' visible='true'>
    <nd ref='-25386' />
    <nd ref='-25390' />
    <nd ref='-25391' />
    <nd ref='-25387' />
    <nd ref='-25386' />
    <tag k='height' v='6' />
    <tag k='building:part' v='yes' />
    <tag k='roof:colour' v='red' />
    <tag k='roof:direction' v='357' />
    <tag k='roof:height' v='3' />
    <tag k='roof:shape' v='skillion' />
  </way>
  <way id='-682' action='modify' visible='true'>
    <nd ref='-25388' />
    <nd ref='-25391' />
    <nd ref='-25390' />
    <nd ref='-25389' />
    <nd ref='-25388' />
    <tag k='height' v='6' />
    <tag k='building:part' v='yes' />
    <tag k='roof:colour' v='red' />
    <tag k='roof:direction' v='177' />
    <tag k='roof:height' v='3' />
    <tag k='roof:shape' v='skillion' />
  </way>
  <way id='-818' action='modify' visible='true'>
    <nd ref='-25394' />
    <nd ref='-25404' />
    <nd ref='-25405' />
    <nd ref='-25396' />
    <nd ref='-25394' />
    <tag k='height' v='5.5' />
    <tag k='building:part' v='yes' />
    <tag k='roof:colour' v='red' />
    <tag k='roof:direction' v='357' />
    <tag k='roof:height' v='1' />
    <tag k='roof:shape' v='skillion' />
  </way>
</osm>

A bit more graphical:


Create blue and green area as described earlier. In this case I added height information to theroof:ridge (motorway).
Build another red subarea from the roof:ridge(trunk) and the roof:dormer (primary). roof:dormer-way suppose to have a height, from ground to top of dormer roof. roof:ridge need another height in this case or might end up at the main ridge.

@karlos unfortunatly, I’m unable to code that idea, but it would be really awesome you or @Zkir could give it a shot. It feels feasible for me and worth a shot. I would think, there are some edge cases, need further thoughts…

1 Like

I think, you are on the right track. But there is only a raw concept for an algorithm; you need to develop quite more details. No coding, just words.

That algorithm will work for dormer to. II will think about dormer later. We need some tagged values, but if a roof hight is on the building or the ridge way can be decided later to.

I am not a native English speaker. Your motorway, primary and trunk puzzled me a lot. And the colours are hard to see. But I think, I know what you mean.

I will stick with the first example: If we assume, ridges are horizontal, the Node, I marked above already defines the height of the blue ridge. The gray? slope the node is in, is defined by the footprint and the roof height. So the node position defines the height at that point. A simple formula. We need to avoid redundancy (but will see them by the user anyway).

So I will try to help to workout an algorithm, assuming the footprint, ridges and height(s) are given.

  1. To split the area, the grey corners form the ridge nodes to the footprint outer seems the really unsolved problem. Can you invent a logic for this?
  2. This done, the footprint shape can be split in a multi-polygon.
  3. Most node heights are calculable by the wall- or ridge/roof- height(s) now.
  4. The marked point by a formula by the relation of distance to the blue ridge and the line below it. If you tag the height, the node position needs to get calculated and moved. The formula is not a thing. But to detect that the point needs calculated, and by what lines is.
  5. Done. With all sub-areas defined by nodes and heights, they can get triangulated and rendered. Also the walls below the footprint.

This draft is not detailed to write a code. You need to define the details, how it could be done.

Dann besser auf Deutsch?
Ja, das war erstmal nur das Konzept. Bleiben wir mal beim einfachen Fall, alle roof:edge=yes enden am building=yes.

Der Grundgedanke ist: Folge solange den Kanten, bis du von einem roten Punkt zu einem anderen gekommen bist.

Der Algorithmus wäre dann:
Finde alle roof:ridge=yes und roof:edge=yes innerhalb des Gebäudes. Ermittle alle Nodes, die Teil von mehreren Ways sind Nummern (1..11), im folgende Stützpunkte genannt. Teile das Gebäude in die Segmente A..H anhand der Stützpunkte, die Teil vom Gebäude sind (1..8). Gleiches Prinzip für die roof:ridge=yes und roof:edge=yes, sodass man die Segmente R..Z hat.
Evtl. muss man auch roof:ridge=yes mit weiteren roof:ridge=yes ways verbinden.

Starte bei Segment A, finde StĂĽtzpunkt (1). Gehe durch alle roof:ridge=yes` der auch den StĂĽtzpunkt (1) haben. In dem Fall ist es nur Z. Finde den anderen StĂĽtzpunkt (9) der Kante.

Finde alle anderen roof:edge=yes, die den Stützpunkt (9) haben. In dem Fall wäre das Y. Wenn die Kante auch Stützpunkt (2) hat (der andere Stützpunkt von A) dann war es ein Dreieck und du kannst deine Fläche aus den drei Kanten AZY bilden, wobei (9) der höchste Punkt ist und die Richtung normal zu A wäre. Wenn du nicht bei (2) raus kommst war es kein Dreieck und es geht weiter bis es keine weiteren Kanten gibt. Wäre hier der Fall, weil AZY endet bei (8).

Finde alle roof:ridge=yes, die den Stützpunkt (9) haben und finde dessen zweiten Stützpunkt. In dem Fall gibt es nur Kante U welche zum Stützpunkt (10) führt. Wenn (10) auch der zweite Stützpunkt von A ist, dann ist die Fläche AZU, wobei U der höchste “Punkt” ist und die Richtung wäre Normal zu U. Wäre hier nicht der Fall, aber bspw. bei BTR oder FXW.

Finde alle roof:edge=yes die in (10) beginnen. Eine Fläche mit mehr als einer roof:ridge=yes kann es nicht geben. In dem Fall gibt es T und S. S führt zu einem roten Stützpunkt, aber nicht dem richtigen. T führt zu (2). Die Fläche ist also AZUT wobei die Richtung normal zu U ist.

FĂĽr DVXGYUS habe ich noch keine Idee. Das scheint mir der komplizierteste Fall zu sein.

Ebenso könnte es den Fall geben, dass die Fläche gar keine Außenkante hat und nur aus roof:edge=yes, roof:ridge=yes und noch einer roof:edge=yes besteht. Hier wäre meine Idee, die Flächeninhalte am Ende zu berechnen und mit dem kompletten Gebäude zu vergleichen. Fehlt noch was, muss man alle roten Stützpunkte durchgehen und von da die Kanten suchen.

Evtl. sowas in die Richtung wenn ich mit dem Algorithum oben nicht von (4) nach (5) komme, merke die die Kanten und Stützpunkt, hier G/H und (8), B/C und (3) und A/H und (1). Mache das gleiche nochmal mit dem anderen Stützpunkt von D, hier (5). Da bekomme ich dann E/F und (6) und G/F (7). Geschlossene Flächen wären hier: SRC, EXW und DVXGYUS. Ich denke, es sollte hier nur eine sinnvolle Kombination möglich sein, die zu einer geschlossenen Fläche führt, die die Kante D beinhaltet. die Normale zu U (wegen roof:ridge=yes) ergäbe dann die Richtung.

Lese noch.
Dreiecke von Flächen machen wir nicht selbst sondern mit “earcut”.

@Tordanik kann gut Deutsch. What about @Zkir?

Ah, wenn du auch die edges taggst, vermeidest du den Algorithmus dafür. Meine historische Datensparsamkeint quengelt: Können wir dann nicht gleich ein einfaches 3D Format anwenden (List von Raumpunkten und Liste von Dreiecken)
Das Einteilen in Sub-Area ist soweit machbar. Irgendwie muß man ab allen Footprint-Ecken immer der Richtung mit Linksdrall folgen. Nur wie findet man die Flächen, die nicht am Foorprint sind?
Earcut macht dann die Dreiecke für die Flächen.

Mein Problem sind die Höhen. Und besonders Blau(11). Bei waagerechten ridge müßte eigendlich der Renderer den Punkt festlegen/errechnen. Wenn der schlecht/zu weit vom errrechneten getaggt ist, werden die Flächen drumherum krumm. Müßte man mal in 3D ausprobieren.

Tut mir leid, Leute, das letzte Mal, dass ich Deutsch gelernt habe, war vor 30 Jahren in der Schule, ohne groĂźen Erfolg.

1 Like

Not sure which information is available to you. I would assume you have the height of the building itself and the height of the ridge UR as well as the main roof angle facing ridge W or (11).

Then you should be able to calculate the shortest distance from U to (11) and use sin() or cos(), depending on which angle you have to get the height difference of UR and W.

Not me, you :wink: But I will think about it