{"id":19,"date":"2020-02-24T10:11:56","date_gmt":"2020-02-24T10:11:56","guid":{"rendered":"https:\/\/www.roblsmith.co.uk\/blog\/?p=19"},"modified":"2022-06-20T14:27:20","modified_gmt":"2022-06-20T14:27:20","slug":"building-a-train-departure-board-using-the-openldbws","status":"publish","type":"post","link":"https:\/\/roblsmith.co.uk\/blog\/2020\/02\/building-a-train-departure-board-using-the-openldbws\/","title":{"rendered":"Building a train departure board using the OpenLDBWS"},"content":{"rendered":"<p>So in my first post of programming stuff, I am going to cover on how to integrate with National Rail&#8217;s <a href=\"https:\/\/lite.realtime.nationalrail.co.uk\/openldbws\/\">OpenLDBWS<\/a> to make a departure board.<\/p>\n<p><span style=\"text-decoration: underline;\">What is the OpenLDBWS<br \/>\n<\/span>The OpenLDBWS is the\u00a0Live Departure Boards Web Service. It provides a web service (vs an API) that returns real-time train information from Darwin. Darwin is the official train information engine. Since 2014 the open-access licence came into effect, meaning that individuals and organisations are now free to develop websites, apps and other digital tools with the data.<\/p>\n<p><span style=\"text-decoration: underline;\">Pre-Reqs<br \/>\n<\/span>1. To gain access to the OpenLDBWS data you need to request an access token <a href=\"http:\/\/realtime.nationalrail.co.uk\/OpenLDBWSRegistration\/Registration\">here<\/a><br \/>\n2. You&#8217;ll also need a way to develop, and host your project. e.g. a webhosting provider or use something like Heroku.<\/p>\n<p><span style=\"text-decoration: underline;\">Let&#8217;s Begin.<\/span><br \/>\nAs I am developing a departure\u00a0board I am going to need to use the GetDepBoardWithDetails operation. The list of them is <a href=\"https:\/\/lite.realtime.nationalrail.co.uk\/openldbws\/\">here<\/a>.<\/p>\n<p>I am using the WithDetails endpoint VS the standard getDepBoard, as I would also like to list out extra information such as calling points to display on my output.<\/p>\n<p>Firstly, we need to build a handler to make the SOAP connection, my example is here:<\/p>\n<pre class=\"brush: php; title: ; notranslate\" title=\"\">\r\n&lt;?php\r\n\r\nclass train\r\n{\r\n\t\/\/ Params\r\n\tprivate $soap = NULL; \/\/ soap connection\r\n\tprivate $accessKey = NULL; \/\/ api key\r\n\tprivate $wsdl = 'http:\/\/lite.realtime.nationalrail.co.uk\/OpenLDBWS\/wsdl.aspx';\r\n\t\r\n\t\r\n\t\/\/  PHP will automatically call this function when you create an object from a class\r\n\tfunction __construct($accessKey)\r\n\t{\r\n\t\t$this-&gt;accessKey = $accessKey; \/\/ Retreive accessKey passed when calls are made\r\n\t\t$soapOptions = array(); \/\/ Any options to pass to Soap connection here\r\n\t\t$this-&gt;soapClient = new SoapClient($this-&gt;wsdl,$soapOptions); \/\/ create new SoapClient connection\r\n\t\t\r\n\t\t$soapVar = new SoapVar(array(&quot;ns2:TokenValue&quot;=&gt;$this-&gt;accessKey),SOAP_ENC_OBJECT);\r\n\t\t$soapHeader = new SoapHeader(&quot;http:\/\/thalesgroup.com\/RTTI\/2010-11-01\/ldb\/commontypes&quot;,&quot;AccessToken&quot;,$soapVar);\r\n\t\t$this-&gt;soapClient-&gt;__setSoapHeaders($soapHeader);\r\n\t}\r\n\t\r\n\t\/\/ Make call for train data\r\n\tfunction GetDepBoardWithDetails($numRows, $crs, $filterCrs=&quot;&quot;, $filterType=&quot;&quot;, $timeOffset=&quot;&quot;, $timeWindow=&quot;&quot;)\r\n\t{\r\n\t\t\/\/ Params for the connection\r\n\t\t$params = array();\r\n\t\t$params[&quot;numRows&quot;] = $numRows;\r\n\t\t$params[&quot;crs&quot;] = $crs;\r\n\r\n\t\tif ($filterCrs) $params[&quot;filterCrs&quot;] = $filterCrs;\r\n\t\tif ($filterType) $params[&quot;filterType&quot;] = $filterType;\r\n\t\tif ($timeOffset) $params[&quot;timeOffset&quot;] = $timeOffset;\r\n\t\tif ($timeWindow) $params[&quot;timeWindow&quot;] = $timeWindow;\r\n\t\t\r\n\t\t\/\/ Make the connection\r\n\t\ttry\r\n\t\t{\r\n\t\t\t$response = $this-&gt;soapClient-&gt;GetDepBoardWithDetails($params);\r\n\t\t}\r\n\t\tcatch(SoapFault $soapFault)\r\n\t\t{\r\n\t\t\ttrigger_error(&quot;Something's wrong&quot;, E_USER_ERROR);\r\n\t\t}\r\n\r\n\t\t\/\/ pass data back\r\n\t\treturn (isset($response) ? $response : false);\r\n\t}\r\n}\r\n<\/pre>\n<p>Once we have this file we can interface with the SOAP service with the applicable WSDL file, and I can begin to handle train data.<\/p>\n<p>I am going to return all data and all fields to see what we have to play with, to do this I am going to do the below code&#8230;<\/p>\n<pre class=\"brush: php; title: ; notranslate\" title=\"\">\r\n&lt;?php\r\n\trequire(&quot;soapHandler.php&quot;);\r\n\t$train = new train(&quot;aaaaaaaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa&quot;);\r\n\t$response = $train-&gt;GetDepBoardWithDetails(1,&quot;MBR&quot;);\r\n\theader(&quot;Content-Type: text\/plain&quot;);\r\n\tprint_r($response-&gt;GetStationBoardResult-&gt;trainServices-&gt;service);\r\n\r\n<\/pre>\n<p>The script above will be the basis for the trainData.php file, with the first script which I have named soapHandler.php. You will need to provide the token key which you would have already requested when making a request to the soap service.<\/p>\n<p>For the 2nd parameter, as I live in Middlesbrough, I have provided the CRS (Computer Reservation System) which is the 3 letter station code, as MBR. e.g. for Hull use HUL &#8211; for other stations, there are lists everywhere in order to find their 3 letter codes. I have also passed in the 1st parameter as 1, this is so it will return a maximum of 1 result.<\/p>\n<p>The data I get back includes scheduled times, on-time\/delayed arrival time, platform, calling points, and if there are cancellations and the reason why.<\/p>\n<p>I did have to change my lookup to use Edinburgh as Middlesbrough had no more departures when I was coding this. I figured this out when I got the below response. As the GetStationBoardResult object was empty.<\/p>\n<pre class=\"brush: php; title: ; notranslate\" title=\"\">\r\nstdClass Object\r\n(\r\n    [generatedAt] =&gt; 2020-02-21T23:46:29.3118451+00:00\r\n    [locationName] =&gt; Middlesbrough\r\n    [crs] =&gt; MBR\r\n    [nrccMessages] =&gt; stdClass Object\r\n        (\r\n            [message] =&gt; Array\r\n                (\r\n                    [0] =&gt; stdClass Object\r\n                        (\r\n                            [_] =&gt; The ticket office at this station is currently closed. \r\n                        )\r\n\r\n                    [1] =&gt; stdClass Object\r\n                        (\r\n                            [_] =&gt; &lt;p&gt;The Ticket Vending Machines are currently out of order at this station.&lt;\/p&gt;\r\n                        )\r\n\r\n                )\r\n\r\n        )\r\n\r\n    [platformAvailable] =&gt; 1\r\n)\r\n<\/pre>\n<p>As the data is returned as an object, if for example I wanted to output a list of calling points I can do this:<\/p>\n<pre class=\"brush: php; title: ; notranslate\" title=\"\">\r\n&lt;?php\r\n\trequire(&quot;soapHandler.php&quot;);\r\n\t$train = new train(&quot;aaaaaaaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa&quot;);\r\n\t$response = $train-&gt;GetDepBoardWithDetails(1,&quot;EDB&quot;);\r\n\theader(&quot;Content-Type: text\/plain&quot;);\r\n\r\n\t$callingPoints = $response-&gt;GetStationBoardResult-&gt;trainServices-&gt;service-&gt;subsequentCallingPoints-&gt;callingPointList-&gt;callingPoint;\r\n\t$callingPoints = json_encode($callingPoints);\r\n\t$callingPoints = json_decode($callingPoints, true);\r\n\r\n\r\n\tforeach ($callingPoints as $callPoint)\r\n\t{\r\n\t\tprint $callPoint['locationName'] . PHP_EOL;\r\n\t}\r\n\r\n<\/pre>\n<p>This resulted in:<\/p>\n<p>Haymarket<br \/>\nEdinburgh Park<br \/>\nUphall<br \/>\nLivingston North<br \/>\nBathgate<\/p>\n<p>When it came to checking for nrccMessages messages, interestingly Edinburgh did have a message<\/p>\n<pre class=\"brush: php; title: ; notranslate\" title=\"\">\r\n    [nrccMessages] =&gt; stdClass Object\r\n        (\r\n            [message] =&gt; stdClass Object\r\n                (\r\n                    [_] =&gt; &lt;p&gt;&lt;\/p&gt;\r\n&lt;p&gt;Poor weather is affecting journeys in Scotland. More details and the impact to your journey can be found in &lt;A href=&quot;http:\/\/nationalrail.co.uk\/service_disruptions\/243304.aspx&quot;&gt;Latest Travel News&lt;\/A&gt;.&lt;\/p&gt;\r\n                )\r\n\r\n        )\r\n<\/pre>\n<p>So we could consider seeing if a value for nrccMessages exists, and capture the message to display elsewhere.<\/p>\n<p>Next up is a way of displaying the train information. I am not going to go into details on how to design something pretty, I will knock something basic up &#8211; based off a generic platform information screen. Essentially we just need to pass in details of the calling points, destination, platforms, and if the training is running on time to this template.<\/p>\n<p>In order to generate the needed data from the info-overload, we can do something like:<\/p>\n<pre class=\"brush: php; title: ; notranslate\" title=\"\">\r\n&lt;?php\r\n\trequire(&quot;soapHandler.php&quot;);\r\n\t$train = new train(&quot;aaaaaaaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa&quot;);\r\n\t$response = $train-&gt;GetDepBoardWithDetails(10,&quot;MBR&quot;);\r\n\theader(&quot;Content-Type: text\/plain&quot;);\r\n\r\n\t$response = json_encode($response);\t\r\n\t$response = json_decode($response, true);\r\n\r\n\t$trainData = array();\r\n\r\n\tforeach ($response['GetStationBoardResult']['trainServices']['service'] as $train)\r\n\t{\r\n\t\t$temp = array();\r\n\t\t$temp['destination'] = $train['destination']['location']['locationName'];\r\n\t\t\r\n\t\t$temp['platform'] = $train['platform'];\r\n\t\t$temp['toc'] = $train['operator'];\r\n\t\t$temp['carriages'] = $train['length'];\r\n\t\t\r\n\t\t\/\/ Work out times\r\n\t\t$temp['schd'] = $train['std'];\r\n\t\t$temp['etd'] = $train['etd'];\r\n\t\t\r\n\t\tif ($train['etd'] != 'On time')\r\n\t\t{\r\n\t\t\t\/\/ Train is late, lol.\r\n\t\t\t$schd = strtotime($train['std']);\r\n\t\t\t$etd = strtotime($train['etd']);\r\n\t\t\t$delay = ($etd - $schd) \/ 60;\r\n\t\t\t\r\n\t\t\t$temp['delay'] = $delay;\r\n\t\t\t\r\n\t\t\t\/\/ Is a delay reason given?\r\n\t\t\tif (isset($train['delayReason']))\r\n\t\t\t{\r\n\t\t\t\t$temp['delayReason'] = $train['delayReason'];\r\n\t\t\t}\r\n\t\t}\r\n\t\t\r\n\t\t\/\/ is train cancelled\r\n\t\t$trainCancelled = isset($train['cancelReason']);\r\n\t\t\r\n\t\tif ($trainCancelled)\r\n\t\t{\r\n\t\t\tif ($train['cancelReason'] != '')\r\n\t\t\t{\r\n\t\t\t\t$temp['cancelReason'] = $train['cancelReason'];\r\n\t\t\t}\r\n\t\t\t\r\n\t\t\t$temp['cancelled'] = true;\r\n\t\t}\r\n\t\t\r\n\t\t\/\/ Get callpoints and times\r\n\t\t$temp['callingPoints'] = array();\r\n\t\t\r\n\t\tif (!$trainCancelled)\r\n\t\t{\r\n\t\t\t$trainCallingPoints = $train['subsequentCallingPoints']['callingPointList']['callingPoint'];\r\n\r\n\t\t\t\/\/ Direct train or multiple call points?\r\n\t\t\tif (isAssocArray($trainCallingPoints))\r\n\t\t\t{\r\n\t\t\t\t$temp['callingPoints'] = array(\r\n\t\t\t\t\t'location' =&gt; $trainCallingPoints['locationName'],\r\n\t\t\t\t\t'st' =&gt; $trainCallingPoints['st'],\r\n\t\t\t\t\t'et' =&gt; $trainCallingPoints['et']);\r\n\t\t\t}\r\n\t\t\telse\r\n\t\t\t{\r\n\t\t\t\tforeach ($trainCallingPoints as $callingPoints)\r\n\t\t\t\t{\r\n\t\t\t\t\t$callPoint = array(\r\n\t\t\t\t\t\t'location' =&gt; $callingPoints['locationName'],\r\n\t\t\t\t\t\t'st' =&gt; $callingPoints['st'],\r\n\t\t\t\t\t\t'et' =&gt; $callingPoints['et']);\r\n\r\n\t\t\t\t\t$temp['callingPoints'][] = $callPoint;\r\n\t\t\t\t}\r\n\t\t\t}\r\n\t\t}\r\n\t\t\r\n\t\t\/\/ push data to array\r\n\t\t$trainData[] = $temp;\r\n\t}\r\n\r\nprint_r(json_encode($trainData));\r\n\r\nfunction isAssocArray($arr)\r\n{\r\n    if (array() === $arr) return false;\r\n    return array_keys($arr) !== range(0, count($arr) - 1);\r\n}\r\n<\/pre>\n<p>With the $trainData variable, if I was to do a print_r, I would see meaningful data which can be passed into the template&#8230;<br \/>\nNow I&#8217;ve had to add a &#8220;hacky&#8221; function because some trains were not returning as an associative array, I assume because it was a direct train, as the only stopping listed was the destination.<\/p>\n<p><span style=\"text-decoration: underline;\">Piecing it together<\/span><br \/>\nNow we have some form of template; and the data we need &#8211; we can use AJAX to query the data and update the screen.<\/p>\n<p>I won&#8217;t go into the nitty-gritty of how to do this, essentially we need a basic HTML file and a JavaScript file.<br \/>\nThe JavaScript file will have a setTimeout() function which will do an AJAX call to this PHP file &#8211; the success handler will then parse the JSON response and loop through it. I am then generating the HTML of the table row, then appending it to the table by doing.<\/p>\n<p><code>$(\"#trainTable\").append(html);<\/code><\/p>\n<p>My setTimeout function works like this, loadData() is called on pageload.<\/p>\n<pre class=\"brush: php; title: ; notranslate\" title=\"\">\r\nfunction loadData() \r\n{\r\n\tsetTimeout(function () {\r\n\t\t\t$.ajax({\r\n\t\t\t\turl: &quot;trainData.php&quot;,\r\n\t\t\t\ttype: &quot;GET&quot;,\r\n\t\t\t\tsuccess: function(result) {\r\n\t\t\t\t\thandleResponse(result); \r\n\t\t\t\t\ttimeout = 60000;\r\n\t\t\t\t},\r\n\t\t\t\tcomplete: function(){\r\n\t\t\t\t\tloadData();\r\n\t\t\t\t},\r\n\t\t\t\terror : function(){\r\n\t\t\t\talert('something went wrong');\r\n\t\t\t}\r\n\t\t});\r\n\t}, timeout);\r\n}\r\n<\/pre>\n<p>With my handleResults looking like this<\/p>\n<pre class=\"brush: php; title: ; notranslate\" title=\"\">\r\nfunction handleResponse(result)\r\n{\r\n\tvar data = JSON.parse(result);\r\n\tvar trainData = data['train'];\r\n\t\r\n\t$(&quot;#trainTable&quot;).html('');\r\n\t\t\r\n\tvar index;\r\n\t\r\n\tfor (index = 0; index &lt; trainData.length; ++index)\r\n\t{\r\n\t\tvar borderStyle = '';\r\n\t\tvar carriageInfo = '';\r\n\t\t\r\n\t\tif (index != (trainData.length-1))\r\n\t\t{\r\n\t\t\tborderStyle = 'border-bottom: 1px solid white;';\r\n\t\t}\r\n\t\t\r\n\t\tif (trainData[index]['carriages'] != '-')\r\n\t\t{\r\n\t\t\tcarriageInfo = ', this train has ' + trainData[index]['carriages'] + ' carriages';\r\n\t\t}\r\n\t\t\r\n\t\tvar html = `\r\n\t\t\t&lt;div class=&quot;row&quot;&gt;\r\n\t\t\t\t&lt;div class=&quot;col-sm-1&quot;&gt;` + trainData[index]['schd'] + `&lt;\/div&gt;\r\n\t\t\t\t&lt;div class=&quot;col-sm-8 TrainDestination&quot;&gt;` + trainData[index]['destination'] +`&lt;\/div&gt;\r\n\t\t\t\t&lt;div class=&quot;col-sm-1 text-md-center&quot;&gt;` + trainData[index]['platform'] + `&lt;\/div&gt;\r\n\t\t\t\t&lt;div class=&quot;col-sm-2 text-md-center&quot;&gt;` + trainData[index]['etd'] + `&lt;\/div&gt;\r\n\t\t\t&lt;\/div&gt;\r\n\t\t\t&lt;div  class=&quot;row&quot;&gt;\r\n\t\t\t&lt;\/div&gt;\r\n\t\t\t&lt;div class=&quot;row no-gutters&quot; style=&quot;padding-left: 94px;` + borderStyle + `&quot;&gt;\r\n\t\t\t\t&lt;div class=&quot;col-sm-12 col-md-1 trainfont&quot;&gt;Callling at:&lt;\/div&gt;\r\n\t\t\t\t&lt;div class=&quot;col-sm-8 col-md-8 trainfont&quot;&gt;\r\n\t\t\t\t  &lt;marquee&gt;` + trainData[index]['callingPointsString'] + `&lt;\/marquee&gt;\r\n\t\t\t\t&lt;\/div&gt;\r\n\t\t\t\t&lt;span class=&quot;trainfont&quot;&gt;Operated by ` + trainData[index]['toc'] + ``+\r\n\t\t\t\tcarriageInfo +` &lt;\/span&gt;\r\n\t\t\t&lt;\/div&gt;`;\r\n\t\t\r\n\t\t\r\n\t\t$(&quot;#trainTable&quot;).append(html);\r\n\t}\r\n\t\r\n\t$(&quot;#nrccMessages&quot;).html(data['nrccMessages']);\r\n\t\r\n}\r\n<\/pre>\n<p>You can see my implementation on github <a href=\"https:\/\/github.com\/robsmithy\/trainBoard\" target=\"_blank\" rel=\"noopener\">here<\/a>.<\/p>\n<p>For now that is all, but I have something cool we can do with the JSON train data.<\/p>\n<p>Something that could be done if you was that way inclined, is hooking it up to a Raspberry Pi and run it off a screen&#8230; Pretty nifty if you wanted to sell it as a product to a cafe near a railway station.<\/p>\n<figure id=\"attachment_25\" aria-describedby=\"caption-attachment-25\" style=\"width: 693px\" class=\"wp-caption alignnone\"><img loading=\"lazy\" class=\"wp-image-25 \" src=\"http:\/\/www.roblsmith.co.uk\/blog\/wp-content\/uploads\/2022\/06\/deps2-1024x528-1-300x155.png\" alt=\"\" width=\"693\" height=\"358\" srcset=\"https:\/\/roblsmith.co.uk\/blog\/wp-content\/uploads\/2022\/06\/deps2-1024x528-1-300x155.png 300w, https:\/\/roblsmith.co.uk\/blog\/wp-content\/uploads\/2022\/06\/deps2-1024x528-1-151x78.png 151w, https:\/\/roblsmith.co.uk\/blog\/wp-content\/uploads\/2022\/06\/deps2-1024x528-1-768x396.png 768w, https:\/\/roblsmith.co.uk\/blog\/wp-content\/uploads\/2022\/06\/deps2-1024x528-1.png 1024w\" sizes=\"(max-width: 693px) 100vw, 693px\" \/><figcaption id=\"caption-attachment-25\" class=\"wp-caption-text\">Example of the board showing data from Kings Cross.<\/figcaption><\/figure>\n<p>Anyhoo,<\/p>\n<p>That covers me creating a basic departures board. Pretty easy to implement, however, the responses back from the OpenLDBWS can be inconsistent so you do need to do a few isset checks. For instance, if no platform information is available it doesn&#8217;t return anything vs. returning at least a key with an empty value or; NULL, &#8216;TBD&#8217;, etc..<\/p>\n","protected":false},"excerpt":{"rendered":"<p>So in my first post of programming stuff, I am going to cover on how to integrate with National Rail&#8217;s OpenLDBWS to make a departure board. What is the OpenLDBWS The OpenLDBWS is the\u00a0Live Departure Boards Web Service. It provides a web service (vs an API) that returns real-time train information from Darwin. Darwin is&#8230;<\/p>\n","protected":false},"author":3,"featured_media":76,"comment_status":"open","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"_monsterinsights_skip_tracking":false,"_monsterinsights_sitenote_active":false,"_monsterinsights_sitenote_note":"","_monsterinsights_sitenote_category":0,"spay_email":""},"categories":[8],"tags":[19,21,20],"jetpack_featured_media_url":"https:\/\/roblsmith.co.uk\/blog\/wp-content\/uploads\/2020\/02\/depatureboard1.png","_links":{"self":[{"href":"https:\/\/roblsmith.co.uk\/blog\/wp-json\/wp\/v2\/posts\/19"}],"collection":[{"href":"https:\/\/roblsmith.co.uk\/blog\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/roblsmith.co.uk\/blog\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/roblsmith.co.uk\/blog\/wp-json\/wp\/v2\/users\/3"}],"replies":[{"embeddable":true,"href":"https:\/\/roblsmith.co.uk\/blog\/wp-json\/wp\/v2\/comments?post=19"}],"version-history":[{"count":5,"href":"https:\/\/roblsmith.co.uk\/blog\/wp-json\/wp\/v2\/posts\/19\/revisions"}],"predecessor-version":[{"id":26,"href":"https:\/\/roblsmith.co.uk\/blog\/wp-json\/wp\/v2\/posts\/19\/revisions\/26"}],"wp:featuredmedia":[{"embeddable":true,"href":"https:\/\/roblsmith.co.uk\/blog\/wp-json\/wp\/v2\/media\/76"}],"wp:attachment":[{"href":"https:\/\/roblsmith.co.uk\/blog\/wp-json\/wp\/v2\/media?parent=19"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/roblsmith.co.uk\/blog\/wp-json\/wp\/v2\/categories?post=19"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/roblsmith.co.uk\/blog\/wp-json\/wp\/v2\/tags?post=19"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}