Skip to main content
Question

Splitting line by angle


Instead of splitting a line by distance or specific vertex indexes, I want to split lines whenever the angle to the next segment is not within x degrees.

 

Any ideas on how to accomplish this?

6 replies

For now, I've calculated the angle to a north vector using python, after which I round it to an int so I can group by it when combining lines. See https://knowledge.safe.com/questions/31810/azimuth-to-compass-directionbearing.html

It's not what I wanted and doesn't work for some cases where the difference in bearing is small but the rounding creates a difference.

 

 

Does anyone know a way to combine lines based on an attribute range?

 

Transformers:

0684Q00000ArJzyQAF.png

Python code:

import fme
import fmeobjects
import math

def processFeature(feature):
    coords = feature.getAllCoordinates()
    if len(coords) < 2:
        return
    x1, y1 = coords[0]
    x2, y2 = coords[1]
    degBearing = math.degrees(math.atan2((x2 - x1),(y2 - y1)))
    feature.setAttribute("degBearing", degBearing)

ebygomm
Influencer
Forum|alt.badge.img+39
  • Influencer
  • August 13, 2018
mannershark wrote:

For now, I've calculated the angle to a north vector using python, after which I round it to an int so I can group by it when combining lines. See https://knowledge.safe.com/questions/31810/azimuth-to-compass-directionbearing.html

It's not what I wanted and doesn't work for some cases where the difference in bearing is small but the rounding creates a difference.

 

 

Does anyone know a way to combine lines based on an attribute range?

 

Transformers:

0684Q00000ArJzyQAF.png

Python code:

import fme
import fmeobjects
import math

def processFeature(feature):
    coords = feature.getAllCoordinates()
    if len(coords) < 2:
        return
    x1, y1 = coords[0]
    x2, y2 = coords[1]
    degBearing = math.degrees(math.atan2((x2 - x1),(y2 - y1)))
    feature.setAttribute("degBearing", degBearing)
The method mentioned here might be a possibility

 

 

https://knowledge.safe.com/questions/47675/group-records-based-on-attribute-values.html

 

 


jdh
Contributor
Forum|alt.badge.img+28
  • Contributor
  • August 13, 2018

I've not done that, but I have generalized lines based on the deflection angle.

 

While it is possible to do so using regular transformers, it is several orders of magnitude faster to do so in python.

 

Essentially you would want to get the coordinates of the vertices, for each vertex calculate the deflection angle.

If it's greater than your maximum, then output that point.

 

Send the points and the original lines to a pointOnLineOverlayer

The deflection angle for any vertex i except the start and end vertices can be calculated by taking the the minimum of the absolute value of the difference of the segment angles and their complement

DeflectionAngle = min(abs(a2-a1),360-abs(a2-a1))

 

 

where

a2 = degrees(atan2(Yi+1-Yi, Xi+1-Xi))

a1 = degrees(atan2(Yi-Yi-1, Xi-Xi-1))


jdh
Contributor
Forum|alt.badge.img+28
  • Contributor
  • August 13, 2018

Consider the following:

 

import fme
import fmeobjects
import math


class FeatureProcessor(object):
    def __init__(self):
        pass
    def input(self,feature):
        maxAngle = 10
        pts= feature.getAllCoordinates()
        
        #skip the first and last point
        for i in range(1,len(pts)-1):
            #get previous, current and next point
            p0 = pts[i-1] 
            p1 = pts[i]
            p2 = pts[i+1]
            
            # get segment angles 
            a1 = self.GetAngle(p0,p1)
            a2 = self.GetAngle(p1,p2)
            # calculate deflection between segments
            angle = min(abs(a2-a1),360-abs(a2-a1))
            #points that exceed tolerance
            if angle < maxAngle:
                feat = fmeobjects.FMEFeature(feature)
                p = fmeobjects.FMEPoint(p1[0],p1[1])
                feat.setGeometry(p)
                feat.setAttribute('DEFLECTION',angle)
                self.pyoutput(feat)
    
    def GetAngle(self,pt1, pt2):
        xDiff = pt2[0] - pt1[0]
        yDiff = pt2[1] - pt1[1]
        d = math.degrees(math.atan2(yDiff, xDiff))
        return d

    def close(self):
        pass
This should produce a point at every vertex where the deflection is greater than MaxAngle.

 

Suggestions for improvement would be to take MaxAngle from an attribute on the feature.

 

When using the pointOnLineOverlayer, you may want to use a group by if there is a possibility that your lines overlap (not at node).

 

 

 


takashi
Influencer
  • August 14, 2018
jdh wrote:

Consider the following:

 

import fme
import fmeobjects
import math


class FeatureProcessor(object):
    def __init__(self):
        pass
    def input(self,feature):
        maxAngle = 10
        pts= feature.getAllCoordinates()
        
        #skip the first and last point
        for i in range(1,len(pts)-1):
            #get previous, current and next point
            p0 = pts[i-1] 
            p1 = pts[i]
            p2 = pts[i+1]
            
            # get segment angles 
            a1 = self.GetAngle(p0,p1)
            a2 = self.GetAngle(p1,p2)
            # calculate deflection between segments
            angle = min(abs(a2-a1),360-abs(a2-a1))
            #points that exceed tolerance
            if angle < maxAngle:
                feat = fmeobjects.FMEFeature(feature)
                p = fmeobjects.FMEPoint(p1[0],p1[1])
                feat.setGeometry(p)
                feat.setAttribute('DEFLECTION',angle)
                self.pyoutput(feat)
    
    def GetAngle(self,pt1, pt2):
        xDiff = pt2[0] - pt1[0]
        yDiff = pt2[1] - pt1[1]
        d = math.degrees(math.atan2(yDiff, xDiff))
        return d

    def close(self):
        pass
This should produce a point at every vertex where the deflection is greater than MaxAngle.

 

Suggestions for improvement would be to take MaxAngle from an attribute on the feature.

 

When using the pointOnLineOverlayer, you may want to use a group by if there is a possibility that your lines overlap (not at node).

 

 

 

Alternatively, the PolylineAnalyzer from FME Hub could also be used to calculate the deflection angle on each vertex, since the transformer would output vertex points containing "_angle_between_segments" (0-360 degrees) attribute to them, via the Vertex port.

 

deflection = @min(@Value(_angle_between_segments), 360 - @Value(_angle_between_segments))

I altered the solution from @jdh to create the desired effect of splitting the line at those points.

 

Note that it can behave strangely when you have multilines or other aggregates.
import fme
import fmeobjects
import math

class FeatureProcessor(object):
    def __init__(self):
        pass
    def input(self,feature):
        maxAngle = 10
        pts= feature.getAllCoordinates()
        prev_i = 0
        
        #skip the first and last point
        for i in range(1,len(pts)-1):
            #get previous, current and next point
            p0 = pts[i-1] 
            p1 = pts[i]
            p2 = pts[i+1]
            # get segment angles 
            a1 = self.get_angle(p0,p1)
            a2 = self.get_angle(p1,p2)
            # calculate deflection between segments
            angle = min(abs(a2-a1),360-abs(a2-a1))
            #If angle exceeds maximum, output line so far
            if angle > maxAngle:
                self.output_line(feature, pts[prev_i:i+1])  # Make line from 1st coordinate up to and including i
                prev_i = i
        # Output remainder of line if a cut wasn't required
        if prev_i < len(pts)-1:
            self.output_line(feature, pts[prev_i:len(pts)])

    def output_line(self, feature, points):
        feat = fmeobjects.FMEFeature(feature)
        line = fmeobjects.FMELine(points)
        feat.setGeometry(line)
        self.pyoutput(feat)

    def get_angle(self,pt1, pt2):
        xDiff = pt2[0] - pt1[0]
        yDiff = pt2[1] - pt1[1]
        d = math.degrees(math.atan2(yDiff, xDiff))
        return d
        
    def close(self):
        pass

Cookie policy

We use cookies to enhance and personalize your experience. If you accept you agree to our full cookie policy. Learn more about our cookies.

 
Cookie settings