The original intention of the design tool was to automate the calculation of track geometry. This proved to be relatively easy,
using a simple vector arithmetic package with a triple vector datatype of x,y,orientation
[19], and the xsl:iterate
instruction processing the track component sequences through template application as the
track is “constructed”. For example here is the code to process a straight
element:
<xsl:template match="straight" as="map(*)" mode="makeTrack"> <xsl:param name="start" as="map(*)"/> <xsl:param name="options" as="map(*)" select="map{}" tunnel="true"/> <xsl:variable name="length" select="@length" as="xs:double"/> <xsl:variable name="straight" select="v:new($length, 0) => v:rotateDeg($start?orient)"/> <xsl:variable name="end" select="v:add($start, $straight)"/> <xsl:variable name="path" select="p:line($start, $end)"/> <xsl:variable name="pieces" as="element()*"> <g class="straight"> <g class="schematic"> <path d="{$path}"/> <xsl:sequence select="r:join($end)"/> </g> <g class="way" transform="translate({$start?x},{$start?y}) rotate({$start?orient})"> <xsl:if test="$options?layTrack"> <xsl:sequence select="r:straight($length)"/> </xsl:if> </g> </g> </xsl:variable> <xsl:sequence select="map{ 'type':string(name()), 'orient.start' : $start?orient, 'orient.end' : $start?orient, 'pieces': $pieces, 'length': $length, 'path': $path, 'start' : $start, 'end': $end, 'name': string((@name, 'S-'||string(accumulator-before('trackNo')))[1]) }" /> </xsl:template>
$start
is an input parameter which is a map whose principal members are x
, y
and
orient
[20]. The new end point, including its orientation, is calculated effectively by
v:add($start, v:new($length,0) => v:rotateDeg($start?orient))
where v:rotateDeg($in,$rot)
rotates a vector (and its end orientation) by $rot
degrees. During this
operation the (SVG) graphic pieces for the schematic and the track pictures are constructed (see below) and added to the resulting map
as well as other needed information, such as track section length. Each piece is named, using an xsl:accumulator
to
generate something suitable in the absence of a specific @name
value.
This template is executed from an xsl:iterate
instruction processing the children of a layout
or a
spur
:
<xsl:template match="rail|spur|layout" as="map(*)*" mode="makeTrack"> <xsl:param name="start" as="map(*)"> <xsl:apply-templates select="start" mode="#current"/> </xsl:param> <xsl:iterate select="* except (start | link)"> <xsl:param name="start" select="$start" as="map(*)"/> <xsl:choose> <xsl:when test="not(self::break)"> <xsl:variable name="part" as="map(*)"> <xsl:apply-templates select="." mode="#current"> <xsl:with-param name="start" select="$start"/> </xsl:apply-templates> </xsl:variable> <xsl:sequence select="$part"/> <xsl:next-iteration> <xsl:with-param name="start" select="$part?end"/> </xsl:next-iteration> </xsl:when> <xsl:otherwise> <xsl:break/> </xsl:otherwise> </xsl:choose> </xsl:iterate> </xsl:template>
For each subsequent iteration the $start
parameter becomes the end
property of the $part
just
generated. Needless to say processing a curve
is similar to that for straight
, though the calculation of the
chord, end point and the appropriate SVG ellipitical arc are more complex. For the point
we need to construct
two sections: the not set (straight on) track section and its end point, and the
set section with its attached branch line, which is constructed by a recursive call on the iteration above,
with the branch spur
element as context and the spur position and orientation as the $start
parameter.
[19] Adding a z
(height) component would be simple, being altered by length * gradient
. It is safe to
assume that gradients will never be steep enough to make significant effects on planar (x,y
) positions.
[20] Orientation is held in degrees and converted to radians as required. SVG describes its rotations in degrees and I know fairly closely what 30°, 45° and 225° look like, but not 1.5 radians.