## 2013-11-03

### Advanced Geometric Operation: Create Normal Lines of a Line Segment

Create normal (perpendicular) lines against a line segment. Make the start node of the resultant lines be equal to the start node of the input line segment.
This image illustrates the expected result.

Workflow Example (FME 2013 SP4 Build 13547)
One possible approach is: offset the line segment so that its start node is located at (0, 0); rotate the line about (0, 0) by 90 (Left) / -90 (Right) degree; scale the lines into specified lengths; offset the scaled lines so that those start nodes are equal to the start node of the input line segment.
If the Scaler origin could be set to any location, the Offsetters would not be necessary. But options of the current Scaler origin are "0, 0" and "Center Point" only, so the workflow became cluttered a little.
A custom transformer which performs scaling based on specified origin coordinate can be created, and it might be useful to not only this workflow but also many scenarios.

Addition 2013-11-04: There is another approach using characteristics of unit vector and normal vector. This workflow looks more elegant and also efficient to me.

Application Examples:
These images were created with the workflow and several existing transformers.

The PythoCaller with this script can do the same operation as the workflow.
-----
# Example for PythonCaller (1): Create Normal Lines of a Line Segment
# FME 2013 SP4 Build 13547
# Assume input feature is a line segment; omit error handling.
import fmeobjects, math

class NormalLineCreator(object):
def __init__(self):
pass

def input(self, feature):
# Check geometry type and number of coordinates.
if feature.getGeometryType() != fmeobjects.FME_GEOM_LINE \
or feature.numCoords() != 2:
return # *** error ***

# Extract coordinate of start node, offset feature,
# and get length of the line segment.
coord = feature.getCoordinate(0)
feature.offset(-coord[0], -coord[1])
lenS = feature.getGeometry().getLength(False)

# Lengths of resultant normal lines.
lenL = float(feature.getAttribute('_length_left'))
lenR = float(feature.getAttribute('_length_right'))

# Create resultant normal lines and output them.
data = [('left', lenL / lenS, 90.0), ('right', lenR / lenS, -90.0)]
for direction, scale, angle in data:
line = feature.clone()
line.rotate2D((0.0, 0.0), angle)
line.scale(scale, scale)
line.offset(coord[0], coord[1])
line.setAttribute('_normal_direction', direction)
self.pyoutput(line)

def close(self):
pass
-----
# 2013-11-04
# Example for PythonCaller (2): Create Normal Lines of a Line Segment
# FME 2013 SP4 Build 13547
# Assume input feature is a line segment; omit error handling.
import fmeobjects, math

class NormalLineCreator(object):
def __init__(self):
pass

def input(self, feature):
# Check geometry type and number of coordinates.
if feature.getGeometryType() != fmeobjects.FME_GEOM_LINE \
or feature.numCoords() != 2:
return # *** error ***

# Calculate unit vector of the line segment.
seg = feature.removeGeometry()
start, end = seg.getPointAt(0), seg.getPointAt(1)
(x0, y0, z0), (x1, y1, z1) = start.getXYZ(), end.getXYZ()
lenS = seg.getLength(False)
ux, uy = (x1 - x0) / lenS, (y1 - y0) / lenS

# Transform the feature to the start node point.
feature.setGeometry(start)

# Lengths of resultant normal lines.
lenL = float(feature.getAttribute('_length_left'))
lenR = float(feature.getAttribute('_length_right'))

# Create resultant normal lines and output them.
data = [('left', lenL, -uy, ux), ('right', lenR, uy, -ux)]
for direction, len, nx, ny in data:
line = feature.clone()
line.addCoordinate(x0 + nx * len, y0 + ny * len, z0)
line.setAttribute('_normal_direction', direction)
self.pyoutput(line)

def close(self):
pass
-----