Computing the geometry

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 class="way" 
          <xsl:if test="$options?layTrack">
            <xsl:sequence select="r:straight($length)"/>
   <xsl:sequence select="map{
        'orient.start' : $start?orient,
        'orient.end' : $start?orient,
        'pieces': $pieces,
        'length': $length,
        'path': $path, 
        'start' : $start,
        'end': $end, 
        'name': string((@name,

$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:iterate select="* except (start | link)">
      <xsl:param name="start" select="$start" as="map(*)"/>
         <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:sequence select="$part"/>
               <xsl:with-param name="start" select="$part?end"/>

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.