Browse Source

Parsing train schedule. Need some work to fill in the missing data.

feature-nr-renew
LO Kam Tao Leo 3 years ago
parent
commit
6fe1ff8c08
  1. 53
      src/main/java/org/leolo/nrdatad/cron/ScheduleImportJob.java
  2. 2
      src/main/java/org/leolo/nrdatad/db/mariadb/DatabaseManager.java
  3. 11
      src/main/java/org/leolo/nrdatad/db/mariadb/TrainAssociationDaoImpl.java
  4. 8
      src/main/java/org/leolo/nrdatad/model/ScheduleAssociation.java
  5. 36
      src/main/java/org/leolo/nrdatad/model/TrainSchedule.java
  6. 41
      src/main/java/org/leolo/nrdatad/model/TrainScheduleLocation.java
  7. 40
      src/main/java/org/leolo/nrdatad/model/TrainScheduleSector.java
  8. 39
      src/main/java/org/leolo/nrdatad/util/DateUtil.java
  9. 6
      src/main/resources/log4j2.xml
  10. 8
      src/test/java/org/leolo/nrdatad/model/ScheduleAssociationTest.java
  11. 20
      src/test/java/org/leolo/nrdatad/model/TrainScheduleTest.java
  12. 41
      src/test/java/org/leolo/nrdatad/util/DateUtilTest.java
  13. 31
      src/test/java/org/leolo/nrdatad/util/TestUtil.java
  14. 178
      src/test/resources/org/leolo/nrdatad/test/V56331_N.json

53
src/main/java/org/leolo/nrdatad/cron/ScheduleImportJob.java

@ -5,8 +5,10 @@ import org.apache.logging.log4j.Logger;
import org.json.JSONObject;
import org.leolo.nrdatad.ConfigurationManager;
import org.leolo.nrdatad.Constants;
import org.leolo.nrdatad.model.ScheduleAssociation;
import org.leolo.nrdatad.model.Tiploc;
import org.leolo.nrdatad.util.HttpUtil;
import org.leolo.nrdatad.util.TUIDDateFormat;
import org.quartz.Job;
import org.quartz.JobExecutionContext;
import org.quartz.JobExecutionException;
@ -124,7 +126,56 @@ public class ScheduleImportJob implements Job {
private void processAssociation(String file){
log.atDebug().log("Processing {}",file);
try(
BufferedReader br = new BufferedReader(
new InputStreamReader(
new GZIPInputStream(
new FileInputStream(
new File(baseTempDir, file)
)
)
)
);
PrintWriter pw = new PrintWriter(new File(baseTempDir, file+"_v"))
){
int count = 0;
ArrayList<ScheduleAssociation> scheduleAssociations = new ArrayList<>();
TUIDDateFormat tuidDateFormat = new TUIDDateFormat();
while(true){
String line = br.readLine();
if(line == null){
break;
}
ScheduleAssociation sa = ScheduleAssociation.parseJSON(line);
scheduleAssociations.add(sa);
pw.println(sa.getMainUid()+","+sa.getAssoUid()+","+sa.getAssociationLocation()+","+
tuidDateFormat.format(sa.getStartDate())+","+
tuidDateFormat.format(sa.getEndDate())+","+
sa.getStpIndicator());
if(scheduleAssociations.size()>1000){
try{
ConfigurationManager.getInstance().getDatabaseManager().getTrainAssociationDao().replaceAll(scheduleAssociations);
} catch (SQLException e){
log.atWarn().withThrowable(e).log("Unable to update records. {}",file);
}
log.atDebug().log("Processed a batch of associations");
scheduleAssociations.clear();
}
}
try{
ConfigurationManager.getInstance().getDatabaseManager().getTrainAssociationDao().replaceAll(scheduleAssociations);
} catch (SQLException e){
log.atWarn().withThrowable(e).log("Unable to update records. {}", file);
}
log.atDebug().log("Processed last batch of associations");
} catch (IOException e){
log.atError().withThrowable(e).log("Error when reading file.");
}
try{
new File(baseTempDir, file).delete();
} catch (Exception e){
log.atWarn().withThrowable(e).log("Unable to remove temp file {}", file);
}
}
private void processTiploc(String file){

2
src/main/java/org/leolo/nrdatad/db/mariadb/DatabaseManager.java

@ -83,6 +83,6 @@ public class DatabaseManager implements org.leolo.nrdatad.db.DatabaseManager{
@Override
public TrainAssociationDao getTrainAssociationDao() {
return null;
return new TrainAssociationDaoImpl(this);
}
}

11
src/main/java/org/leolo/nrdatad/db/mariadb/TrainAssociationDaoImpl.java

@ -159,12 +159,12 @@ public class TrainAssociationDaoImpl extends TrainAssociationDao {
setDate(pstmt, 3, scheduleAssociation.getStartDate());
setDate(pstmt, 4, scheduleAssociation.getEndDate());
setString(pstmt, 5, scheduleAssociation.getAssoDays());
setString(pstmt, 6, scheduleAssociation.getAssociationCategory().getCode());
setString(pstmt, 6, scheduleAssociation.getAssociationCategory()==null?"":scheduleAssociation.getAssociationCategory().getCode());
setInt(pstmt, 7, scheduleAssociation.getAssociationDate());
setString(pstmt, 8, scheduleAssociation.getAssociationLocation());
setString(pstmt, 9, scheduleAssociation.getBaseLocationSuffix());
setString(pstmt, 10, scheduleAssociation.getAssocLocationSuffix());
setString(pstmt, 11, scheduleAssociation.getStpIndicator().getCode());
setString(pstmt, 11, scheduleAssociation.getStpIndicator()==null?"":scheduleAssociation.getStpIndicator().getCode());
setInt(pstmt, 12, scheduleAssociation.hashCode());
setString(pstmt, 13, scheduleAssociation.getAuid());
}
@ -191,7 +191,7 @@ public class TrainAssociationDaoImpl extends TrainAssociationDao {
int nopCount = 0;
try (
Connection conn = getConnection();
PreparedStatement psIns = conn.prepareStatement("INSERT INTO train_association (" +
PreparedStatement psIns = conn.prepareStatement("INSERT IGNORE INTO train_association (" +
"auid, main_uid, asso_uid, start_date, end_date, " + //1-5
"asso_days, asso_type, date_ind, asso_loc, base_suffix, " + //6-10
"asso_suffix, stp_ind, hash_code, last_chk) " + //11-14
@ -225,6 +225,7 @@ public class TrainAssociationDaoImpl extends TrainAssociationDao {
psIns.executeBatch();
psNop.executeBatch();
psUpd.executeBatch();
conn.commit();
log.atDebug().log("Batch info: {} Inserted. {} Updated. {} NOP.", insCount, updCount, nopCount);
}
}
@ -236,12 +237,12 @@ public class TrainAssociationDaoImpl extends TrainAssociationDao {
setDate(psIns, 4, scheduleAssociation.getStartDate());
setDate(psIns, 5, scheduleAssociation.getEndDate());
setString(psIns, 6, scheduleAssociation.getAssoDays());
setString(psIns, 7, scheduleAssociation.getAssociationCategory().getCode());
setString(psIns, 7, scheduleAssociation.getAssociationCategory()==null?"":scheduleAssociation.getAssociationCategory().getCode());
setInt(psIns, 8, scheduleAssociation.getAssociationDate());
setString(psIns, 9, scheduleAssociation.getAssociationLocation());
setString(psIns, 10, scheduleAssociation.getBaseLocationSuffix());
setString(psIns, 11, scheduleAssociation.getAssocLocationSuffix());
setString(psIns, 12, scheduleAssociation.getStpIndicator().getCode());
setString(psIns, 12, scheduleAssociation.getStpIndicator()==null?"":scheduleAssociation.getStpIndicator().getCode());
setInt(psIns, 13, scheduleAssociation.hashCode());
}
}

8
src/main/java/org/leolo/nrdatad/model/ScheduleAssociation.java

@ -28,7 +28,7 @@ public class ScheduleAssociation {
private TUIDDateFormat tuidDateFormat = new TUIDDateFormat();
public String getAuid(){
return mainUid+assoUid+associationLocation+tuidDateFormat.format(startDate)+tuidDateFormat.format(endDate);
return mainUid+assoUid+associationLocation+tuidDateFormat.format(startDate)+tuidDateFormat.format(endDate)+stpIndicator.getCode();
}
public String getMainUid() {
@ -148,16 +148,16 @@ public class ScheduleAssociation {
result = 31 * result + startDate.hashCode();
result = 31 * result + endDate.hashCode();
result = 31 * result + assoDays.hashCode();
result = 31 * result + associationCategory.name().hashCode();
result = 31 * result + (associationCategory==null?0:associationCategory.name().hashCode());
result = 31 * result + associationDate;
result = 31 * result + associationLocation.hashCode();
result = 31 * result + (baseLocationSuffix != null ? baseLocationSuffix.hashCode() : 0);
result = 31 * result + (assocLocationSuffix != null ? assocLocationSuffix.hashCode() : 0);
result = 31 * result + stpIndicator.name().hashCode();
result = 31 * result + (stpIndicator==null?0:stpIndicator.name().hashCode());
return result;
}
public static ScheduleAssociation parseJOSN(String obj) throws JSONException {
public static ScheduleAssociation parseJSON(String obj) throws JSONException {
return parseJSON(new JSONObject(obj));
}

36
src/main/java/org/leolo/nrdatad/model/TrainSchedule.java

@ -1,10 +1,16 @@
package org.leolo.nrdatad.model;
import org.json.JSONObject;
import org.leolo.nrdatad.util.DateUtil;
import java.util.Date;
public class TrainSchedule {
private int scheduleVersion = 0;
private int scheduleVersion = UNKNOWN_VERSION_NUMBER;
public static final int UNKNOWN_VERSION_NUMBER = -1;
private String runsOnBankHoliday;
private String trainStatus;//TODO: change it into enum
private String trainUid;
@ -15,6 +21,7 @@ public class TrainSchedule {
private String tractionClass;
private Date startDate;
private Date endDate;
private TrainScheduleSector sector;
public String getRunsOnBankHoliday() {
return runsOnBankHoliday;
@ -95,4 +102,31 @@ public class TrainSchedule {
public void setEndDate(Date endDate) {
this.endDate = endDate;
}
public TrainScheduleSector getSector() {
return sector;
}
public void setSector(TrainScheduleSector sector) {
this.sector = sector;
}
public static TrainSchedule parseJSON(String json){
return parseJSON(new JSONObject(json));
}
public static TrainSchedule parseJSON(JSONObject object){
TrainSchedule ts = new TrainSchedule();
ts.runsOnBankHoliday = object.optString("CIF_bank_holiday_running");
ts.trainUid = object.optString("CIF_train_uid");
ts.runsOn = object.optString("schedule_days_runs");
ts.shortTermPlanningIndicator = ShortTermPlanningIndicator.parseCode(object.optString("CIF_stp_indicator"));
ts.atocCode = object.optString("atoc_code");
ts.startDate = DateUtil.parseDate(object.optString("schedule_start_date"));
ts.endDate = DateUtil.parseDate(object.optString("schedule_end_date"));
ts.sector = TrainScheduleSector.parseJSON(object.getJSONObject("schedule_segment"));
return ts;
}
}

41
src/main/java/org/leolo/nrdatad/model/TrainScheduleLocation.java

@ -1,12 +1,16 @@
package org.leolo.nrdatad.model;
import org.json.JSONObject;
import org.leolo.nrdatad.util.TimeUtil;
public class TrainScheduleLocation {
private int recordSequence = 0;
public static final int UNKNOWN_RECORD_SEQUENCE = -1;
private int recordSequence = UNKNOWN_RECORD_SEQUENCE;
private TrainScheduleSector recordSector;
private TrainScheduleLocationRecordIdentity recordIdentity;
private String tiploc;
private int tiplocInstance;
private int tiplocInstance = UNKNOWN_RECORD_SEQUENCE;
private long wttArrival;
private long wttDeparture;
private long wttPass;
@ -159,4 +163,37 @@ public class TrainScheduleLocation {
public void setPerformanceAllowance(long performanceAllowance) {
this.performanceAllowance = performanceAllowance;
}
public static TrainScheduleLocation parseJSON(String json){
return parseJSON(new JSONObject(json), UNKNOWN_RECORD_SEQUENCE);
}
public static TrainScheduleLocation parseJSON(String json, int recordSequence){
return parseJSON(new JSONObject(json), recordSequence);
}
public static TrainScheduleLocation parseJSON(JSONObject object){
return parseJSON(object, UNKNOWN_RECORD_SEQUENCE);
}
public static TrainScheduleLocation parseJSON(JSONObject object, int recordSequence){
TrainScheduleLocation tsl = new TrainScheduleLocation();
tsl.recordSequence = recordSequence;
tsl.tiploc = object.optString("tiploc_code");
tsl.tiplocInstance = object.optInt("tiploc_instance", UNKNOWN_RECORD_SEQUENCE);
tsl.wttArrival = TimeUtil.parseTime(object.optString("arrival"));
tsl.wttDeparture = TimeUtil.parseTime(object.optString("departure"));
tsl.wttPass = TimeUtil.parseTime(object.optString("pass"));
tsl.publicArrival = TimeUtil.parseTime(object.optString("public_arrival"));
tsl.publicDeparture = TimeUtil.parseTime(object.optString("public_departure"));
tsl.platform = object.optString("platform");
tsl.line = object.optString("line");
tsl.path = object.optString("path");
tsl.engineeringAllowance = TimeUtil.parseTime(object.optString("engineering_allowance"));
tsl.pathingAllowance = TimeUtil.parseTime(object.optString("pathing_allowance"));
tsl.performanceAllowance = TimeUtil.parseTime(object.optString("performance_allowance"));
tsl.recordIdentity = TrainScheduleLocationRecordIdentity.parseCode(object.optString("location_type"));
return tsl;
}
}

40
src/main/java/org/leolo/nrdatad/model/TrainScheduleSector.java

@ -1,5 +1,11 @@
package org.leolo.nrdatad.model;
import org.json.JSONArray;
import org.json.JSONObject;
import java.util.List;
import java.util.Vector;
public class TrainScheduleSector {
/**
* A sequence number of the schedule sector which are belongs to same schedule
@ -86,6 +92,8 @@ public class TrainScheduleSector {
*/
private String trainClass;
private List<TrainScheduleLocation> scheduleLocations = new Vector<>();
public int getSectorId() {
return sectorId;
}
@ -205,4 +213,36 @@ public class TrainScheduleSector {
public void setTrainClass(String trainClass) {
this.trainClass = trainClass;
}
public List<TrainScheduleLocation> getScheduleLocations() {
return scheduleLocations;
}
public static TrainScheduleSector parseJSON(String json){
return parseJSON(new JSONObject(json));
}
public static TrainScheduleSector parseJSON(JSONObject object){
TrainScheduleSector tss = new TrainScheduleSector();
tss.trainCategory = object.optString("CIF_train_category");
tss.signalId = object.optString("signalling_id");
tss.headcode = object.optString("CIF_headcode");
tss.trainServiceCode = object.optString("CIF_train_service_code");
tss.businessSector = object.optString("CIF_business_sector");
tss.powerType = object.optString("CIF_power_type");
tss.timingLoad = object.optString("CIF_timing_load");
tss.speed = object.optInt("CIF_speed");
tss.operatingCharacteristic = object.optString("CIF_operating_characteristics");
tss.trainClass = object.optString("COF_train_class");
tss.sleeper = object.optString("CIF_sleepers");
tss.reservation = object.optString("COF_reservations");
tss.catering = object.optString("CIF_catering_code");
tss.serviceBranding = object.optString("CIF_service_branding");
JSONArray locations = object.getJSONArray("schedule_location");
for(int i=0;i<locations.length();i++){
JSONObject location = locations.getJSONObject(i);
tss.scheduleLocations.add(TrainScheduleLocation.parseJSON(location));
}
return tss;
}
}

39
src/main/java/org/leolo/nrdatad/util/DateUtil.java

@ -0,0 +1,39 @@
package org.leolo.nrdatad.util;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.Date;
public class DateUtil {
private static Logger log = LogManager.getLogger();
public static final String DEFAULT_DATE_FORMAT = "yyyy-MM-dd";
public static Date parseDate(String date){
return parseDate(date, DEFAULT_DATE_FORMAT, true);
}
public static Date parseDate(String date, boolean suppressException){
return parseDate(date, DEFAULT_DATE_FORMAT, suppressException);
}
public static Date parseDate(String data, String dateFormat){
return parseDate(data, dateFormat, true);
}
public static Date parseDate(String data, String dateFormat, boolean suppressException){
try{
return new SimpleDateFormat(dateFormat).parse(data);
} catch (ParseException e) {
log.atWarn().withThrowable(e).log("Unable to parse date");
if(suppressException){
return null;
}
throw new RuntimeException(e.getMessage(), e);
}
}
}

6
src/main/resources/log4j2.xml

@ -5,10 +5,7 @@
<PatternLayout
pattern="%d{HH:mm:ss.SSS} [%t] %-5level %logger{36} - %msg%n" />
</Console>
<File name="RIlog" fileName="ri_log">
<PatternLayout
pattern="%d{HH:mm:ss.SSS} [%t] %-5level %logger{36} - %msg%n" />
</File>
</Appenders>
<Loggers>
<Root level="debug">
@ -16,7 +13,6 @@
</Root>
<Logger name="org.leolo.nrdd">
<AppenderRef ref="Console" />
<AppenderRef ref="RIlog" />
</Logger>
</Loggers>
</Configuration>

8
src/test/java/org/leolo/nrdatad/model/ScheduleAssociationTest.java

@ -15,7 +15,7 @@ public class ScheduleAssociationTest {
"\"date_indicator\":\"S\",\"base_location_suffix\":null,\"main_train_uid\":\"L77935\"," +
"\"CIF_stp_indicator\":\"P\",\"assoc_start_date\":\"2022-05-16T00:00:00Z\"," +
"\"location\":\"KNGX\",\"category\":\"NP\"}";
ScheduleAssociation sa = ScheduleAssociation.parseJOSN(json);
ScheduleAssociation sa = ScheduleAssociation.parseJSON(json);
SimpleDateFormat sdf = new SimpleDateFormat("yyyyMMdd");
assertEquals(sdf.parse("20221205"), sa.getEndDate());
assertEquals("", sa.getAssocLocationSuffix());
@ -28,7 +28,7 @@ public class ScheduleAssociationTest {
assertEquals(sdf.parse("20220516"), sa.getStartDate());
assertEquals("KNGX", sa.getAssociationLocation());
assertEquals(AssociationCategory.NEXT, sa.getAssociationCategory());
assertEquals("L77935L77547KNGXPFE4", sa.getAuid());
assertEquals("L77935L77547KNGXPFE4P", sa.getAuid());
assertEquals(790828983, sa.hashCode());
}
@ -38,7 +38,7 @@ public class ScheduleAssociationTest {
"\"date_indicator\":\"S\",\"base_location_suffix\":null,\"main_train_uid\":\" 77935\"," +
"\"CIF_stp_indicator\":\"P\",\"assoc_start_date\":\"2022-05-16T00:00:00Z\"," +
"\"location\":\"KNGX\",\"category\":\"NP\"}";
ScheduleAssociation sa = ScheduleAssociation.parseJOSN(json);
ScheduleAssociation sa = ScheduleAssociation.parseJSON(json);
SimpleDateFormat sdf = new SimpleDateFormat("yyyyMMdd");
assertEquals(sdf.parse("20221205"), sa.getEndDate());
assertEquals("", sa.getAssocLocationSuffix());
@ -51,7 +51,7 @@ public class ScheduleAssociationTest {
assertEquals(sdf.parse("20220516"), sa.getStartDate());
assertEquals("KNGX", sa.getAssociationLocation());
assertEquals(AssociationCategory.NEXT, sa.getAssociationCategory());
assertEquals("7793577547KNGXPFE4", sa.getAuid());
assertEquals("7793577547KNGXPFE4P", sa.getAuid());
assertEquals(-1968067017, sa.hashCode());
}

20
src/test/java/org/leolo/nrdatad/model/TrainScheduleTest.java

@ -0,0 +1,20 @@
package org.leolo.nrdatad.model;
import org.junit.Test;
import org.leolo.nrdatad.util.TestUtil;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import static org.junit.Assert.*;
public class TrainScheduleTest {
@Test public void testSchedule1() throws IOException{
String json = TestUtil.openResourceFileAsString("org/leolo/nrdatad/test/V56331_N.json");
TrainSchedule ts = TrainSchedule.parseJSON(json);
assertEquals("V56331", ts.getTrainUid());
//TODO: finish the function
}
}

41
src/test/java/org/leolo/nrdatad/util/DateUtilTest.java

@ -0,0 +1,41 @@
package org.leolo.nrdatad.util;
import org.junit.Test;
import java.util.Calendar;
import java.util.Date;
import java.util.GregorianCalendar;
import static org.junit.Assert.*;
public class DateUtilTest {
@Test public void testDate1(){
// Second day of January 2022
Date d1 = DateUtil.parseDate("2022-01-02");
Calendar c = GregorianCalendar.getInstance();
c.setTime(d1);
assertEquals(2022, c.get(Calendar.YEAR));
assertEquals(Calendar.JANUARY, c.get(Calendar.MONTH));
assertEquals(2, c.get(Calendar.DAY_OF_MONTH));
}
@Test public void testDate2(){
// Second day of January 2022
Date d1 = DateUtil.parseDate("2022-01-02", "yyyy-dd-MM");
Calendar c = GregorianCalendar.getInstance();
c.setTime(d1);
assertEquals(2022, c.get(Calendar.YEAR));
assertEquals(Calendar.FEBRUARY, c.get(Calendar.MONTH));
assertEquals(1, c.get(Calendar.DAY_OF_MONTH));
}
@Test public void testDateInvalidSuppressed(){
assertNull(DateUtil.parseDate("This is not a date"));
}
@Test(expected = RuntimeException.class) public void testDateException(){
DateUtil.parseDate("This is not a date", false);
}
}

31
src/test/java/org/leolo/nrdatad/util/TestUtil.java

@ -0,0 +1,31 @@
package org.leolo.nrdatad.util;
import java.io.*;
public class TestUtil {
public static BufferedReader openResourceFileAsBufferedReader(String fileName) throws IOException {
ClassLoader classloader = Thread.currentThread().getContextClassLoader();
InputStream is = classloader.getResourceAsStream(fileName);
if(is==null){
throw new FileNotFoundException("Resource file "+fileName+" cannot be found");
}else{
return new BufferedReader(new InputStreamReader(is));
}
}
public static String openResourceFileAsString(String fileName) throws IOException {
ClassLoader classloader = Thread.currentThread().getContextClassLoader();
InputStream is = classloader.getResourceAsStream(fileName);
if(is==null){
throw new FileNotFoundException("Resource file "+fileName+" cannot be found");
}
ByteArrayOutputStream baos = new ByteArrayOutputStream();
is.transferTo(baos);
String result = new String(baos.toByteArray());
baos.close();
is.close();
return result;
}
}

178
src/test/resources/org/leolo/nrdatad/test/V56331_N.json

@ -0,0 +1,178 @@
{
"CIF_bank_holiday_running": null,
"train_status": "1",
"CIF_train_uid": "V56331",
"schedule_days_runs": "0010000",
"CIF_stp_indicator": "N",
"applicable_timetable": "Y",
"atoc_code": "AW",
"schedule_start_date": "2023-01-04",
"new_schedule_segment": {
"uic_code": "",
"traction_class": ""
},
"transaction_type": "Create",
"schedule_end_date": "2023-01-04",
"schedule_segment": {
"CIF_service_branding": "",
"CIF_train_category": "OO",
"CIF_business_sector": "??",
"CIF_connection_indicator": null,
"CIF_speed": "075",
"CIF_reservations": null,
"CIF_catering_code": null,
"CIF_power_type": "DMU",
"CIF_course_indicator": 1,
"CIF_timing_load": "S",
"signalling_id": "2F60",
"CIF_headcode": "",
"CIF_operating_characteristics": null,
"schedule_location": [
{
"pathing_allowance": null,
"record_identity": "LO",
"engineering_allowance": null,
"line": null,
"public_departure": "1654",
"tiploc_code": "PTYPRID",
"tiploc_instance": null,
"departure": "1654",
"performance_allowance": null,
"location_type": "LO",
"platform": "1"
},
{
"engineering_allowance": null,
"arrival": "1656",
"pass": null,
"line": null,
"public_arrival": "1656",
"performance_allowance": null,
"location_type": "LI",
"platform": null,
"pathing_allowance": null,
"record_identity": "LI",
"path": null,
"public_departure": "1657",
"tiploc_code": "TREFRST",
"tiploc_instance": null,
"departure": "1657"
},
{
"engineering_allowance": null,
"arrival": "1703",
"pass": null,
"line": null,
"public_arrival": "1704",
"performance_allowance": null,
"location_type": "LI",
"platform": null,
"pathing_allowance": null,
"record_identity": "LI",
"path": null,
"public_departure": "1704",
"tiploc_code": "TAFFSWL",
"tiploc_instance": null,
"departure": "1704"
},
{
"engineering_allowance": null,
"arrival": "1707",
"pass": null,
"line": null,
"public_arrival": "1708",
"performance_allowance": null,
"location_type": "LI",
"platform": "1",
"pathing_allowance": null,
"record_identity": "LI",
"path": null,
"public_departure": "1708",
"tiploc_code": "RADYR",
"tiploc_instance": null,
"departure": "1708"
},
{
"engineering_allowance": null,
"arrival": "1710",
"pass": null,
"line": null,
"public_arrival": "1710",
"performance_allowance": null,
"location_type": "LI",
"platform": null,
"pathing_allowance": null,
"record_identity": "LI",
"path": null,
"public_departure": "1711",
"tiploc_code": "LLANDAF",
"tiploc_instance": null,
"departure": "1711"
},
{
"engineering_allowance": "1",
"arrival": "1714H",
"pass": null,
"line": null,
"public_arrival": "1715",
"performance_allowance": null,
"location_type": "LI",
"platform": null,
"pathing_allowance": "H",
"record_identity": "LI",
"path": null,
"public_departure": "1715",
"tiploc_code": "CATHAYS",
"tiploc_instance": null,
"departure": "1715H"
},
{
"engineering_allowance": null,
"arrival": "1719",
"pass": null,
"line": null,
"public_arrival": "1720",
"performance_allowance": null,
"location_type": "LI",
"platform": "3",
"pathing_allowance": null,
"record_identity": "LI",
"path": null,
"public_departure": "1721",
"tiploc_code": "CARDFQS",
"tiploc_instance": null,
"departure": "1721"
},
{
"engineering_allowance": null,
"arrival": null,
"pass": "1723",
"line": null,
"public_arrival": null,
"performance_allowance": null,
"location_type": "LI",
"platform": null,
"pathing_allowance": null,
"record_identity": "LI",
"path": null,
"public_departure": null,
"tiploc_code": "CVLESBY",
"tiploc_instance": null,
"departure": null
},
{
"record_identity": "LT",
"path": null,
"arrival": "1724",
"public_arrival": "1725",
"tiploc_code": "CRDFCEN",
"tiploc_instance": null,
"location_type": "LT",
"platform": "7"
}
],
"CIF_sleepers": null,
"CIF_train_service_code": "25441000",
"CIF_train_class": "S"
}
}
Loading…
Cancel
Save