  October 03, 2016.

# How to Keep your Train from going off the Rails

Thirty years ago, I received a beautiful electric train set.It came with three different kinds of rails: Thirty years ago, I received a beautiful electric train set.

It came with three different kinds of rails: 1. straight rails,
2. curved rails,
3. and… curved rails… what ? Again?!

Years later, this third type of rail still puzzled me. The second kind of rail was definitely arc-shaped, but the third had a funny curve to it.

One thing was sure: when I would build an oval run, if I didn’t put the right rails in the right place, my train was guaranteed to derail at high speed. ## All aboard the clothoid!

Enough with the teaser. The mystery curve was called a clothoid, or Euler spiral, or just spiral among friends.

A clothoid is the result of a specific mathematical formula that defines a curve whose curvature changes linearly with its curve length.

By definition, the radius of a curve is:

• the radius of a circle (in our case, the second type of rail),
• infinity for straight rails (i.e. the same rails with an infinite radius).

Following the sequence below (called “S-C-S” for Spiral-Curve-Spiral):

• rail-straight, R = +∞,
• rail-spiral, R : +∞ → 60 cm,
• rail-curve, R = 60 cm,
• rail-spiral, R : 60 cm → +∞,
• rail-straight, R = +∞,

the train trip becomes much smoother, without the risk of derailments!

To better understand the effect, let’s take another example from railroads: The blue curve is exclusively made up of arcs from a circle with a 60-cm radius, while the green and red curve is made up of two different spirals.

If we now represent the curve (1/R) along the road, this is what we get: The second itinerary is longer, but free of curvature breaks. This type of curve has many applications in civil engineering: for roads, bridges, railroads, mechanics.

Remember this next time you take a highway exit safely at 100 km/h!

## Why are clothoids also called spirals?

Looking at the math behind the curve, if you draw such a curve on an infinite trajectory, this is what you get:

Two symmetrical spirals.

R Radius of curvature at any given point in a trajectory.Given that R = +∞ at the beginning of the trajectory. Angle between the tangent to the trajectory at any given point and the axis of the abscissa.φ = 0 at the beginning. Distance travelled along the curve at any given point.L = 0 at the beginning. Constant describing the change in the radius of the curve along the trajectory, by definition:1/R = L x 1/A2, i.e. A2 = RL Note that in this figure, the two φ angles are identical:

• φ = internal angle between (d1) and (d2).
• (d3) is perpendicular to (d1) by construction.
• (d4) is perpendicular to (d2), since it is the tangent of circle CM at point M.
• Therefore the angle between (d3) and (d4) is also φ.

## Let’s draw a clothoid!

Now let’s draw our own spiral.

If we imagine the trajectory of point M(x,y) along the following itinerary:

• from the curved abscissa L1 (=0),
• to the curved abscissa L2 (=L length of trajectory),

we get: Note that φ is indeed a function of l: So, given: we get: which gives us the following JavaScript code:


function drawSpiral(A, L1, L2)
{
var a  = A*Math.sqrt(2);
var S1 = L1/a;
var S2 = L2/a;

var s=S1;
var ds = Math.abs(S2-S1)/10000;

var M = new Point(0,0);

while (s


## Result

.wrapper_result{position:relative}.canvas_img{position:absolute;float:left;background-color:#e8e8e8;border:2px solid #777;border-radius:10px;padding:10px;margin:10px;box-shadow:0 12px 16px 0 rgba(0,0,0,0.24),0 17px 50px 0 rgba(0,0,0,0.19)}@media all and (max-width:940px){.canvas_img{position:initial;float:none}.wrapper_canvas canvas{width:100%;height:auto}}

• Length :
var canvas=document.getElementById("myCanvas");var ctx=canvas.getContext("2d");function f2str(f,digit){if(digit===undefined)digit=1;var p=Math.pow(10,digit);return Math.round(f*p)/p} function clear(){ctx.clearRect(0,0,canvas.width,canvas.height);xaxis(0);yaxis(0);xmark(1);ymark(1)} var MINX=-10;var MAXX=+10;var MINY=-10;var WIDTH=(MAXX-MINX);var HEIGHT=WIDTH;var MAXY=MINY+WIDTH;var SCALE=canvas.width/WIDTH;function coord(pt){return new Point(SCALE*(pt.x-MINX),canvas.height-SCALE*(pt.y-MINY))} function Point(vx,vy){this.x=vx;this.y=vy;this.add=function(p){return new Point(this.x+p.x,this.y+p.y)} this.norm=function(){return Math.sqrt(this.x*this.x+this.y*this.y)}} function Vector(vstart,vangle,vlenght){this.start=new Point(vstart.x,vstart.y);this.angle=vangle;this.lenght=vlenght;this.end=function(){return new Point(this.start.x+this.lenght*Math.cos(this.angle),this.start.y+this.lenght*Math.sin(this.angle))} this.middle=function(){var e=this.end();return new Point(0.5*this.start.x+0.5*e.x,0.5*this.start.y+0.5*e.y)}} function Vector2(p1,p2){var diff=new Point(p2.x-p1.x,p2.y-p1.y);var angle=Math.atan2(diff.y,diff.x);var lenght=diff.norm();return new Vector(p1,angle,lenght)} function lineTo(pt){var pt2=coord(pt);ctx.lineTo(pt2.x,pt2.y)} function moveTo(pt){var pt2=coord(pt);ctx.moveTo(pt2.x,pt2.y)} function line(a,b){moveTo(a);lineTo(b)} function yaxis(y,w,l){if(!w)w=1;if(!l)l=WIDTH;ctx.beginPath();line(new Point(-l,y),new Point(+l,y));ctx.lineWidth=w;ctx.strokeStyle='#555';ctx.stroke()} function xaxis(x,w,l){if(!w)w=1;if(!l)l=HEIGHT;ctx.beginPath();line(new Point(x,-l),new Point(x,+l));ctx.lineWidth=w;ctx.strokeStyle='#555';ctx.stroke()} function xmark(s){for(x=0;x<MAXX;x+=s)xaxis(x,1,HEIGHT/100);for(x=0;x>MINX;x-=s)xaxis(x,1,HEIGHT/100);} function ymark(s){for(y=0;y<MAXY;y+=s)yaxis(y,1,WIDTH/100);for(y=0;y>MINY;y-=s)yaxis(y,1,WIDTH/100);} function ray(v,name,color){var p1=v.start;var p2=v.end();var p3=new Vector(p2,v.angle+9*Math.PI/10,WIDTH/50).end();var p4=new Vector(p2,v.angle-9*Math.PI/10,WIDTH/50).end();var p5=v.middle();if(!name)name=""+f2str(p2.x-p1.x)+", "+f2str(p2.y-p1.y)+")";if(!color)color='#FCC';ctx.beginPath();ctx.lineWidth=0.5;line(p1,p2);ctx.strokeStyle=color;ctx.stroke();ctx.beginPath();ctx.lineWidth=0.5;moveTo(p3);lineTo(p4);lineTo(p2);lineTo(p3);ctx.fillStyle=color;ctx.fill();ctx.strokeStyle="#333";ctx.stroke();ctx.fillStyle=color;ctx.font="12px Arial";var pt2=coord(p5);ctx.fillText(name,pt2.x+7,pt2.y-7);dot(v.start," ",color)} function dot(p,name,color){ctx.beginPath();var pt2=coord(p);if(!name)name="("+f2str(p.x)+", "+f2str(p.y)+")";if(!color)color='#CCC';line(new Point(p.x-WIDTH/100,p.y),new Point(p.x+HEIGHT/100,p.y));line(new Point(p.x,p.y-HEIGHT/100),new Point(p.x,p.y+HEIGHT/100));ctx.lineWidth=1;ctx.strokeStyle=color;ctx.stroke();ctx.beginPath();ctx.arc(pt2.x,pt2.y,5,0,2*Math.PI);ctx.fillStyle=color;ctx.fill();ctx.stroke();ctx.fillStyle=color;ctx.font="12px Arial";ctx.fillText(name,pt2.x+7,pt2.y-7)} function circle(p,radius,color){ctx.beginPath();var pt2=coord(p);if(!color)color='#CCC';line(new Point(p.x-WIDTH/100,p.y),new Point(p.x+HEIGHT/100,p.y));line(new Point(p.x,p.y-HEIGHT/100),new Point(p.x,p.y+HEIGHT/100));ctx.lineWidth=1;ctx.strokeStyle=color;ctx.stroke();ctx.beginPath();ctx.arc(pt2.x,pt2.y,SCALE*radius,0,2*Math.PI);ctx.strokeStyle=color;ctx.stroke()} clear()function drawEulerSpiral(A,L1,L2,isCCW){var a=A*Math.sqrt(2);var l1=L1/a;var l2=L2/a;var dx,dy;var s=l1;var ds=Math.abs(l2-l1)/10000;var M=new Point(0,0);var signe=((isCCW)?1:-1);var firstDelta=null;var lastDelta=null;var testLenght=0;ctx.beginPath();ctx.lineWidth=2;ctx.strokeStyle="#F00";while(s<l2){dx=a*Math.cos(s*s)*ds;dy=signe*a*Math.sin(s*s)*ds;s+=ds;var delta=new Point(dx,dy);var M2=M.add(delta);testLenght+=delta.norm();if(!firstDelta)firstDelta=new Vector2(M,M2);lastDelta=new Vector2(M,M2);M=M2;lineTo(M2)} ctx.stroke();var inRadius=null;if(L1!=0)inRadius=A*A/L1;var inStrRadius=(!inRadius)?"+inf":""+f2str(inRadius);var outRadius=null;if(L2!=0)outRadius=A*A/L2;var outStrRadius=(!outRadius)?"+inf":""+f2str(outRadius);firstDelta.lenght=3;ray(firstDelta,"input (R="+inStrRadius+")","#F88");lastDelta.lenght=3;ray(lastDelta,"output (R="+outStrRadius+")","#88E");var inAngle=(L1*L1)/(a*a);var outAngle=(L2*L2)/(a*a);if(inRadius){firstDelta.lenght=Math.abs(inRadius);firstDelta.angle+=((inRadius<0)?-1:1)*signe*Math.PI/2;circle(firstDelta.end(),firstDelta.lenght,"#755")} if(outRadius){lastDelta.lenght=Math.abs(outRadius);lastDelta.angle+=((outRadius<0)?-1:1)*signe*Math.PI/2;circle(lastDelta.end(),lastDelta.lenght,"#557")}} function ifcclothoidalarcsegment2d(Lenght,StartRadius,isCCW,isEntry,ClothoideConstant){var A=ClothoideConstant;var L=Lenght;var startL;if(isEntry){startL=0;if(StartRadius!=null)startL=A*A/StartRadius}else{startL=-L;if(StartRadius!=null)startL=-A*A/StartRadius;isCCW=!isCCW} stopL=startL+L;drawEulerSpiral(A,startL,stopL,isCCW)} function onClear(){clear()} function onClick(){var lenght=Number(document.getElementById("lenght").value);var StartRadius=Number(document.getElementById("StartRadius").value);if(isNaN(StartRadius)||StartRadius<=0)StartRadius=null;var isCCW=Number(document.getElementById("isCCW").value);var isEntry=Number(document.getElementById("isEntry").value);var ClothoideConstant=Number(document.getElementById("ClothoideConstant").value);ifcclothoidalarcsegment2d(lenght,StartRadius,isCCW,isEntry,ClothoideConstant)} onClick()