Skip to main content

Hi FME-users,

How to check the spatial relationship between a line and polygon with a Pythoncaller transformer ?

I'm calculating the longest possible line inside a polygon feature that has a certain orientation and that does not intersect with the geometry of the polygon itself. First I calculated all possible lines, their distances and orientation between all vertices of the polygon feature. Next I want to retrieve the longest line that is completely situated within the polygon and that has a certain orientation.Therefore I need to check the spatial relationship, i.e. does the line intersect with the polygon?

I want to perform this calculation completely within python (PythonCaller) without using external libraries.

Thanks in advance,

Dries Verdoodt

You can achieve that using fmeobjects.FMEFactoryPipeline. However, depending on what you want to do, this can become very complex. Let me know if you want an example.


You can achieve that using fmeobjects.FMEFactoryPipeline. However, depending on what you want to do, this can become very complex. Let me know if you want an example.

Thanks Iarry, Yes I would like to have an example please.


Hello Dries,

First of all, you can access FME factories documentation here: http://docs.safe.com/fme/html/FME_FactFunc/index.h...

And here is how I use a factory with python.

If I have this transformer in FME Workbench:

0684Q00000ArLOOQA3.png

I can cut&paste; it in a text editor to see the factory code:

DEFAULT_MACRO WB_CURRENT_CONTEXT
# -------------------------------------------------------------------------
FACTORY_DEF * SpatialRelationshipFactory                               \
   FACTORY_NAME SpatialRelator                                         \
   INPUT BASE FEATURE_TYPE AttributeCreator_OUTPUT_0_olPiq+C1D00=      \
   INPUT CANDIDATE FEATURE_TYPE AttributeCreator_2_OUTPUT_1_KpUyI6tMDVI= \
   PREDICATE "INTERSECTS"                                              \
   LIST_NAME "_relationships"                                          \
   SUCCESS_ATTR "_related_candidates"                                  \
   REJECT_INVALID_GEOM Yes                                             \
   CURVE_BOUNDARY_RULE ENDPOINTS_MOD2                                  \
   CALCULATE_CARDINALITY No                                            \
   OUTPUT TAGGED FEATURE_TYPE SpatialRelator_Output                    \
   OUTPUT REJECTED FEATURE_TYPE SpatialRelator_<Rejected>

I can then use that factory code in a python caller this way (note that I renamed some parameters and cleaned up some quotes (")):

import fme
import fmeobjects


# Template Class Interface:
class FeatureProcessor(object):
def __init__(self):
#Initialize the pipeline
strFactory = """FACTORY_DEF * SpatialRelationshipFactory                               \
FACTORY_NAME SpatialRelator                                         \
INPUT BASE FEATURE_TYPE RequestorPort              \
INPUT CANDIDATE FEATURE_TYPE SupplierPort       \
PREDICATE INTERSECTS                                              \
LIST_NAME _relationships                                          \
SUCCESS_ATTR _related_candidates                                  \
REJECT_INVALID_GEOM Yes                                             \
CURVE_BOUNDARY_RULE ENDPOINTS_MOD2                                  \
CALCULATE_CARDINALITY No                                            \
OUTPUT TAGGED FEATURE_TYPE SpatialRelator_Output                    \
OUTPUT REJECTED FEATURE_TYPE SpatialRelator_<Rejected>"""

self.pipeline = fmeobjects.FMEFactoryPipeline("MyFactory")
self.pipeline.addFactory(strFactory)


def input(self,feature):
#Push features in the pipeline
type = feature.getAttribute("_type")
if type == "LINE":
print "Adding a line"
feature.setFeatureType("RequestorPort")
self.pipeline.processFeature(feature)
elif type == "AREA":
print "Adding an area"
feature.setFeatureType("SupplierPort")
self.pipeline.processFeature(feature)
else:
raise Exception("Unexpected feature!")

def close(self):
#Adding one more line
myFeature = fmeobjects.FMEFeature()
myFeature.addCoordinates(p(10,30),(100,30)])
myFeature.setFeatureType("RequestorPort")
self.pipeline.processFeature(myFeature)

#Execute the factory code
self.pipeline.allDone()

tmpFeature = self.pipeline.getOutputFeature()
while tmpFeature != None:
#We skip the rejected features
if tmpFeature.getFeatureType() == "SpatialRelator_Output":
self.pyoutput(tmpFeature)
tmpFeature = self.pipeline.getOutputFeature()

<br>

Sample workspace is attached (FME 2015).

Regards,

Larry


Hello Dries,

First of all, you can access FME factories documentation here: http://docs.safe.com/fme/html/FME_FactFunc/index.h...

And here is how I use a factory with python.

If I have this transformer in FME Workbench:

0684Q00000ArLOOQA3.png

I can cut&paste; it in a text editor to see the factory code:

DEFAULT_MACRO WB_CURRENT_CONTEXT
# -------------------------------------------------------------------------
FACTORY_DEF * SpatialRelationshipFactory                               \
   FACTORY_NAME SpatialRelator                                         \
   INPUT BASE FEATURE_TYPE AttributeCreator_OUTPUT_0_olPiq+C1D00=      \
   INPUT CANDIDATE FEATURE_TYPE AttributeCreator_2_OUTPUT_1_KpUyI6tMDVI= \
   PREDICATE "INTERSECTS"                                              \
   LIST_NAME "_relationships"                                          \
   SUCCESS_ATTR "_related_candidates"                                  \
   REJECT_INVALID_GEOM Yes                                             \
   CURVE_BOUNDARY_RULE ENDPOINTS_MOD2                                  \
   CALCULATE_CARDINALITY No                                            \
   OUTPUT TAGGED FEATURE_TYPE SpatialRelator_Output                    \
   OUTPUT REJECTED FEATURE_TYPE SpatialRelator_<Rejected>

I can then use that factory code in a python caller this way (note that I renamed some parameters and cleaned up some quotes (")):

import fme
import fmeobjects


# Template Class Interface:
class FeatureProcessor(object):
def __init__(self):
#Initialize the pipeline
strFactory = """FACTORY_DEF * SpatialRelationshipFactory                               \
FACTORY_NAME SpatialRelator                                         \
INPUT BASE FEATURE_TYPE RequestorPort              \
INPUT CANDIDATE FEATURE_TYPE SupplierPort       \
PREDICATE INTERSECTS                                              \
LIST_NAME _relationships                                          \
SUCCESS_ATTR _related_candidates                                  \
REJECT_INVALID_GEOM Yes                                             \
CURVE_BOUNDARY_RULE ENDPOINTS_MOD2                                  \
CALCULATE_CARDINALITY No                                            \
OUTPUT TAGGED FEATURE_TYPE SpatialRelator_Output                    \
OUTPUT REJECTED FEATURE_TYPE SpatialRelator_<Rejected>"""

self.pipeline = fmeobjects.FMEFactoryPipeline("MyFactory")
self.pipeline.addFactory(strFactory)


def input(self,feature):
#Push features in the pipeline
type = feature.getAttribute("_type")
if type == "LINE":
print "Adding a line"
feature.setFeatureType("RequestorPort")
self.pipeline.processFeature(feature)
elif type == "AREA":
print "Adding an area"
feature.setFeatureType("SupplierPort")
self.pipeline.processFeature(feature)
else:
raise Exception("Unexpected feature!")

def close(self):
#Adding one more line
myFeature = fmeobjects.FMEFeature()
myFeature.addCoordinates(p(10,30),(100,30)])
myFeature.setFeatureType("RequestorPort")
self.pipeline.processFeature(myFeature)

#Execute the factory code
self.pipeline.allDone()

tmpFeature = self.pipeline.getOutputFeature()
while tmpFeature != None:
#We skip the rejected features
if tmpFeature.getFeatureType() == "SpatialRelator_Output":
self.pyoutput(tmpFeature)
tmpFeature = self.pipeline.getOutputFeature()

<br>

Sample workspace is attached (FME 2015).

Regards,

Larry

Hello Pierre, 

Thank you very much for your answer. This works fine, but I need to do something more specifically.

The PythonCaller I'm using will only accept geometry features. The line features will be created inside the PythonCaller. Next this line should be compared with the geometry of the polygon entering the transformer.

E.g. creating a line object inside the PythonCaller after line 39:

    # Create line feature
    myFeature = fmeobjects.FMEFeature()
    myFeature.addCoordinates(t(10,30),(100,30)])
    myFeature.setFeatureType("RequestorPort")
    #self.pipeline.processFeature(myFeature) 

However, If i create a new line feature inside the PythonCaller, the PythonCaller no longer runs correctly.

Is it possible to use fmeobjects.FMEFactoryPipeline with both objects entering the PythonCaller and objects created in the PythonCaller itself?


Hello Dries,

First of all, you can access FME factories documentation here: http://docs.safe.com/fme/html/FME_FactFunc/index.h...

And here is how I use a factory with python.

If I have this transformer in FME Workbench:

0684Q00000ArLOOQA3.png

I can cut&paste; it in a text editor to see the factory code:

DEFAULT_MACRO WB_CURRENT_CONTEXT
# -------------------------------------------------------------------------
FACTORY_DEF * SpatialRelationshipFactory                               \
   FACTORY_NAME SpatialRelator                                         \
   INPUT BASE FEATURE_TYPE AttributeCreator_OUTPUT_0_olPiq+C1D00=      \
   INPUT CANDIDATE FEATURE_TYPE AttributeCreator_2_OUTPUT_1_KpUyI6tMDVI= \
   PREDICATE "INTERSECTS"                                              \
   LIST_NAME "_relationships"                                          \
   SUCCESS_ATTR "_related_candidates"                                  \
   REJECT_INVALID_GEOM Yes                                             \
   CURVE_BOUNDARY_RULE ENDPOINTS_MOD2                                  \
   CALCULATE_CARDINALITY No                                            \
   OUTPUT TAGGED FEATURE_TYPE SpatialRelator_Output                    \
   OUTPUT REJECTED FEATURE_TYPE SpatialRelator_<Rejected>

I can then use that factory code in a python caller this way (note that I renamed some parameters and cleaned up some quotes (")):

import fme
import fmeobjects


# Template Class Interface:
class FeatureProcessor(object):
def __init__(self):
#Initialize the pipeline
strFactory = """FACTORY_DEF * SpatialRelationshipFactory                               \
FACTORY_NAME SpatialRelator                                         \
INPUT BASE FEATURE_TYPE RequestorPort              \
INPUT CANDIDATE FEATURE_TYPE SupplierPort       \
PREDICATE INTERSECTS                                              \
LIST_NAME _relationships                                          \
SUCCESS_ATTR _related_candidates                                  \
REJECT_INVALID_GEOM Yes                                             \
CURVE_BOUNDARY_RULE ENDPOINTS_MOD2                                  \
CALCULATE_CARDINALITY No                                            \
OUTPUT TAGGED FEATURE_TYPE SpatialRelator_Output                    \
OUTPUT REJECTED FEATURE_TYPE SpatialRelator_<Rejected>"""

self.pipeline = fmeobjects.FMEFactoryPipeline("MyFactory")
self.pipeline.addFactory(strFactory)


def input(self,feature):
#Push features in the pipeline
type = feature.getAttribute("_type")
if type == "LINE":
print "Adding a line"
feature.setFeatureType("RequestorPort")
self.pipeline.processFeature(feature)
elif type == "AREA":
print "Adding an area"
feature.setFeatureType("SupplierPort")
self.pipeline.processFeature(feature)
else:
raise Exception("Unexpected feature!")

def close(self):
#Adding one more line
myFeature = fmeobjects.FMEFeature()
myFeature.addCoordinates(p(10,30),(100,30)])
myFeature.setFeatureType("RequestorPort")
self.pipeline.processFeature(myFeature)

#Execute the factory code
self.pipeline.allDone()

tmpFeature = self.pipeline.getOutputFeature()
while tmpFeature != None:
#We skip the rejected features
if tmpFeature.getFeatureType() == "SpatialRelator_Output":
self.pyoutput(tmpFeature)
tmpFeature = self.pipeline.getOutputFeature()

<br>

Sample workspace is attached (FME 2015).

Regards,

Larry

Since I don't know what is happening when the PythonCaller stops to run correctly, there are two possible issues:

  • You must call processFeature in the pipeline before calling allDone.
  • Your new code indentation is not correct (I used tabs to indent the python code while you may have used spaces for the new code)

Otherwise, tell me what the error message is and attach the new version of the workspace you have.


Wow, this is interesting!

I just tried to implement this myself 
but using the Chopper. I noticed however that two factories are called 
in the Chopper transformer.

DEFAULT_MACRO WB_CURRENT_CONTEXT

# -------------------------------------------------------------------------
FACTORY_DEF * TeeFactory  \
  FACTORY_NAME Chopper_catcher  \
  OUTPUT FEATURE_TYPE __chopper_input__  \
  "_remnant" no

FACTORY_DEF * ChoppingFactory  \
  FACTORY_NAME Chopper  \
  INPUT FEATURE_TYPE __chopper_input__  \
  MODE VERTEX  \
  REMNANT_ATTRIBUTE "_remnant"  \
  CHOP_POLYGONS  \
  GRID_POLYGONS

 

Larry, do you care for elaborating how I could implement this using Python? 

I am struggling with the TeeFactory since it doesn't seem to have an input. Should I add this factory to the pipeline? How does this work? 

Thanks for your answer!


Since I don't know what is happening when the PythonCaller stops to run correctly, there are two possible issues:

  • You must call processFeature in the pipeline before calling allDone.
  • Your new code indentation is not correct (I used tabs to indent the python code while you may have used spaces for the new code)

Otherwise, tell me what the error message is and attach the new version of the workspace you have.

Hi Larry,

The indentation was indeed not correct. Although it seemed the indentation was correct at first sight, I added some code using spaces as a delimiter while you were using tabs.

Thank you very much for your response and the support to solve this question.


Hi,

First of all, you should connect you input and output transformer ports before cutting and pasting it. If you connect them, you'll have a better factory.

You can also look at the factory documentation to get some clues about the TeeFactory.

In the following image, I connected a Creator to the input port and an Inspector to the output one.

You now have input on the TeeFactory and output on the ChoppingFactory (1 and 2).

Also note that the output of the TeeFactory (3) is connected to the input of the ChopperFactory(4).

My understanding is that the TeeFactory creates a _remnant attribute with "no" as the default value and that the ChoppingFactory will set it to "yes" if need be.

0684Q00000ArLQLQA3.png

You now have three options to integrate this code in a PythonCaller.

  1. Just use the ChoppingFactory: correctly chopped features will not have the _remnant="no" attribute and remnant features will have the _remnant="yes" attribute.
  2. Just use the ChoppingFactory and create the _remnant="no" attribute on the features before pushing them into the factory: you'll have the same behavior as the transformer.
  3. Add both factories to the pipeline: you'll have the same behavior as the transformer.

This python code demonstrates option 3. Note that since the ChopperFactory is not blocking, I can read back the chopped features right after pushing one for processing instead of having to wait in the close method in the case of a blocking factory like SpatialRelator.

import fme
import fmeobjects

# Template Class Interface:
class FeatureProcessor(object):
def __init__(self):
#Initialize the pipeline
strFactory1 = """FACTORY_DEF * TeeFactory  \
   FACTORY_NAME Chopper_catcher                    \
   INPUT  FEATURE_TYPE Creator_2_CREATED           \
   OUTPUT FEATURE_TYPE __chopper_input__           \
       _remnant no"""


strFactory2 = """FACTORY_DEF * ChoppingFactory  \
   FACTORY_NAME Chopper                                 \
   INPUT FEATURE_TYPE __chopper_input__                 \
   MODE VERTEX                                          \
   MAX_VERTICES 2                                       \
   REMNANT_ATTRIBUTE _remnant                           \
   CHOP_POLYGONS                                        \
   OUTPUT CHOPPED FEATURE_TYPE Chopper_OUTPUT           \
        _chopped yes                                    \
   OUTPUT UNTOUCHED FEATURE_TYPE Chopper_OUTPUT         \
        _chopped no"""


self.pipeline = fmeobjects.FMEFactoryPipeline("MyFactory")
self.pipeline.addFactory(strFactory1)
self.pipeline.addFactory(strFactory2)

def input(self,feature):
#Push features in the pipeline
feature.setFeatureType("Creator_2_CREATED")
self.pipeline.processFeature(feature)
tmpFeature = self.pipeline.getOutputFeature()
while tmpFeature != None:
self.pyoutput(tmpFeature)
tmpFeature = self.pipeline.getOutputFeature()

def close(self):
pass


Regards,

Larry


Reply