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?
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?
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:
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)
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:
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)
https://knowledge.safe.com/questions/47675/group-records-based-on-attribute-values.html
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))
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).
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).
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