Question

SpatialRelator with 3D data

  • 21 November 2014
  • 20 replies
  • 35 views

Hi all,

 

 

I have a set of 3D data (roofs) that are badly organised: 1 feature for all roofs geometries. I would like to create for each building a single feature containg all its roofs. Geometrically speaking, I would like to merge all triangles sharing a common edge in a single feature.

 

 

I thus first use a Deagregator to split the input feature into triangles. I then use a SpatialRelator testing for INTERSECTS. However, the output is not what I expect. Some triangles with the same "_related_candidates" value are not intersecting. Screenshots attached illustrate the problem.

 

 

Could you please point me in the right firection?

 

 

Best regards.

20 replies

Badge +3
Hi,

 

 

Check the feature information window.

 

They might be intersecting ( fme says so, so it must be true..)

 

 

Just, it might be by touching in a point rather then line (edge). Or even overlapping somewhere.

 

 

You could set "calculate cardinality" to yes and check this.

 

( the relevant information is in a list  "_relationshsips{}card_point" etc.

 

pass criteria is nest one level deeper "_relationships{}pass{}")
Userlevel 2
Badge +17
Hi,

 

 

I agree with Gio.

 

The SpatialRelator with "INTERSECTS" detects not only the feature sharing boundary but also the feature touched at a vertex. And also, if you have sent all the features to both the Requestor and the Supplier ports, the transformer detects the feature itself for every feature, unless you specify unique feature ID attribute to the "Attributes(s) that Must Differ" parameter.

 

Maybe it's the reason the "_related_candidates" has 3.

 

 

In addition, in my testing, the SpatialRelator (FME 2014 SP4) seems not to determine kind of spatial relationships as expected when the geometries are faces.

 

I would try this.

 

1) To avoid the wrong determination, once transform the triangular faces into 3D polygons with a GeometryCoercer (Geometry Type: fme_polygon).

 

2) Add a SpatialRelator.

 

Tests to Perform: TOUCHES

 

Calculate Cardinality of Intersections: Yes

 

3) Based on the cardinality calculation result, determine which of "line" (sharing boundary) or "point" (touched at a vertex) was the intersected situation.

 

4) After some required processings, re-transform the polygons into faces with a FaceReplacer.

 

5) Group the faces for each building, aggregate them (Aggregator).

 

 

Just be aware that the SpatialRelator (FME 2014) has a bug in the cardinality calculation for overlapping of areas, although it doesn't affect your case. The bug has been fixed in the FME 2015 beta.

 

 

Takashi
Gio, Takashi,

 

 

Thanks for you answers. From your comments, I understood that I misunderstood the meaning of the "_related_candidates" attributes. It is the number of features which pass the test.

 

 

My workbench is currently as follow:

 

Reader --> Deagregator --> GeometryCoercer (fme_polygon) --> SpatialRelator

 

 

The features feeding Requestor and Supplier ports of the SpatialRelator are the same. I have also computed cardinality, and I am able to visualy validate that the TOUCHES test performs well (and allow to differentiate between edge and vertex adjacencies). Now, how can I merge touching faces in a single feature? Does not it require some kind of recursive processing? For instance, if A touches B and B touches C but not A, A, B and C should be merge in a single feature.

 

 

Thanks for you help.
Userlevel 2
Badge +17
The SpatialRelator might not be a best choice for the purpose.

 

The point would be how to group consecutively adjoined triangular polygons. As you mentioned, I think it requires some iterative processing, and possibly scripting will be easier than using only existing transformers.

 

For example:

 

 

The TopologyBuilder will add "_polygon_id" to every polygon, and add "_left_polygon" and "_right_polygon" ("_polygon_id" of left/right hand side) to every line. The "_polygon_id" is 1-based sequential number (0 means "not existing"). 

 

This script example computes group ID ("_group_id") and creates features having a pair of "_group_id" and "_polygon_id".

 

Merge "_group_id" to the polygons and re-transform them into faces. You can then finally aggregate the triangular faces for each group.

 

-----

 

# Script Example for the PythonCaller

 

import fmeobjects

 

 

class GroupIdCalculator(object):

 

    def __init__(self):

 

        # List for set of polygon IDs for each group

 

        self.polygonIdSets = []

 

        

 

    def input(self, feature):

 

        left = int(feature.getAttribute('_left_polygon'))

 

        right = int(feature.getAttribute('_right_polygon'))

 

        if 0 < left and 0 < right:

 

            # Collect polygon IDs in the same group

 

            t , u = [], set([left, right])

 

            for s in self.polygonIdSets:

 

                if left in s or right in s:

 

                    u |= s # Unite sets

 

                else:

 

                    t.append(s)

 

            t.append(u)

 

            self.polygonIdSets = t # Update the list

 

            

 

    def close(self):

 

        for  groupId, s in enumerate(self.polygonIdSets):

 

            for polygonId in s:

 

                feature = fmeobjects.FMEFeature()

 

                feature.setAttribute('_group_id', groupId)

 

                feature.setAttribute('_polygon_id', polygonId)

 

                self.pyoutput(feature)

 

-----

 

Hope this works a treat for you.
Userlevel 2
Badge +17
In a case like this image,

 

 

if you consider the yellow polygons and the gray polygons as parts belonging to a same group (i.e. building), set "No" to the "Assumue Clean Data (Advanced)" parameter of the TopologyBuilder.
Userlevel 2
Badge +17
There is always room for improvement in a script. 

 

This example may be a little more efficient.

 

-----

 

# Script Example for the PythonCaller: Version 2

 

import fmeobjects

 

 

class GroupIdCalculator(object):

 

    def __init__(self):

 

        # Key: Polygon ID, Value: Set of Polygon IDs in same group

 

        self.groups = {}

 

        

 

    def input(self, feature):

 

        left = int(feature.getAttribute('_left_polygon'))

 

        right = int(feature.getAttribute('_right_polygon'))

 

        if 0 < left and 0 < right:

 

            grp = self.groups.setdefault(left, set([left])) | self.groups.setdefault(right, set([right]))

 

            for id in grp:

 

                self.groups[id] = grp

 

            

 

    def close(self):

 

        groupId, finished = 0, set([])

 

        for id, grp in self.groups.items():

 

            if id not in finished:

 

                for polygonId in grp:

 

                    feature = fmeobjects.FMEFeature()

 

                    feature.setAttribute('_group_id', groupId)

 

                    feature.setAttribute('_polygon_id', polygonId)

 

                    self.pyoutput(feature)

 

                groupId += 1

 

                finished |= grp

 

-----
Takashi,

 

 

I would like to thank you for your previous answers. I have tested the solution you proposed using a TopologyBuilder and and a PythonCaller. It does the job as requested!

 

 

I however still have one isue that you might help me with. I set "Compare Z values for duplicates removal" to "No" in my workbench. If I set it to "Yes", it seems that triangular faces are not merged as expected. However, when setting the parameter value to "No", some faces are "wrongly" merged. Indeed, some faces sharing a common edge when projected on a 2D plane are merged, whereas they do not share an edge when kept in 3D.

 

 

The screenshot attached illustrates the problem.

 

 

 

Hope you could help for this last step!

 

 

Best regards,

 

 

Asher
Sorry, forgive to mention that in the last scrrenshot, the orange selection is considered as a single feature.
Userlevel 2
Badge +17
Sorry, I'm not sure which transformer you are talking about. Which transformer does the "Compare Z values for duplicates removal" parameter belong to?
GeometryCoercer has this parameter.

 

 

Would you like to play my with data?
Userlevel 2
Badge +17
Got it.

 

Yes, I'd like to see the actual data if possible.
Userlevel 2
Badge +17
I added three transformers - Chopper, Matcher, SpatialFilter. How's this?

 

Here is a better link: https://www.dropbox.com/s/3l1z085b34rsaki/light.gml?dl=0. Removed previous one ...
Takashi,

 

 

Your solution seems to wok like a charm! I still however have some features not merged, but this is due to geometry (se screenshots 1 & 2). I think I can try to merge correctly using wall surfaces. Will try ...

 

 

Indeed, I am wondering why applying the same workbench using walls as inputs does not give a good result (see screenshot 3). Any idea?

 

 

Best regards,

 

 

Asher

 

 

 

Userlevel 2
Badge +17
A wall has same lines in 2D. Since the TopologyBuilder works in 2D, I don't think the data flow can be applied to the wall surfaces. Maybe we have to discard the TopologyBuilder if you need to process the walls and the roofs together.

 

 

I tried another approach. Do these images illustrate your required results?

 

 

 

Takashi,

 

 

One time more, thanks for your answer. From your screenshot, I can say that this is not exactly the result I am trying to obtain. The firsts results (where merged roofs are smaller: http://fmepedia.safe.com/servlet/rtaImage?eid=907a0000000csD3&feoid=Body&refid=0EMa0000000VCkw) is more the expected result. However, some roof parts are not merged, because they are not adjacent to other roof part, although they obviously belong to the same roof. I am however not sure this can be done.

 

 

This says, your results are very interesting sinc they improve to data. How did you managed to obtain them?
Userlevel 2
Badge +17
This is the workspace that I've used to create the result - grouping roofs and walls together.

 

If you remove the "WallSurface" reader feature type, it generates better roof groups. Maybe it'll be better than the TopologyBuilder solution, but still not be ideal. Some improvements are necessary to make ideal roof groups.

 

 

-----

 

# Script Example for the PythonCaller

 

import fmeobjects

 

 

class GroupIdSetter(object):

 

    def __init__(self):

 

        self.features = {}

 

        self.groups = {}

 

        

 

    def input(self, feature):

 

        matchIds = feature.getAttribute('_match_list{}._tmp_id')

 

        if matchIds == None:

 

            self.features[feature.getAttribute('_tmp_id')] = feature

 

        else:

 

            grp = set([])

 

            for id in matchIds:

 

               grp |= self.groups.setdefault(id, set([id]))

 

            for id in grp:

 

               self.groups[id] = grp 

 

            

 

    def close(self):

 

        groupId = 0

 

        for grp in self.groups.values():

 

            if grp:

 

                while grp:

 

                    feature = self.features[grp.pop()]

 

                    feature.setAttribute('_group_id', groupId)

 

                    self.pyoutput(feature)

 

                groupId += 1

 

-----
Userlevel 2
Badge +17
Removed the "WallSurface" and modified the process following the PythonCaller.

 

Other parts including the script are same as the previous version.

 

 

not elegant...

 

 

I think the resulting roofs are better than the previous results, but not perfect.

 

In this case, ideal automation might be difficult.
Userlevel 2
Badge +17
forgot to attach the output.

 

hello, maybe you will help me with my question..

 

I have shp file with attribute of height and I want to receive citygml file where walls and roofs are separated

Reply