Skip to content

Commit

Permalink
London Tube Departure Display (#290)
Browse files Browse the repository at this point in the history
* Added basic ui structure for tubes

* Improved mobile formatting

* Further work on new tube api

* Added tube api calls

* Fix mobile center container

* Added tube searching

* Fix missing schema file

---------

Co-authored-by: benfl3713 <buses3713@gmail.com>
  • Loading branch information
benfl3713 and ben-fletcher committed Nov 14, 2023
1 parent a6ca3c3 commit 518a393
Show file tree
Hide file tree
Showing 30 changed files with 1,226 additions and 61 deletions.
2 changes: 1 addition & 1 deletion .gitattributes
Original file line number Diff line number Diff line change
Expand Up @@ -65,5 +65,5 @@

DepartureBoardWeb/ClientApp/src/assets/cookie-policy.html linguist-vendored
DepartureBoardWeb/ClientApp/src/assets/privacy-policy.html linguist-vendored
TrainDataAPI/Connected Services/NationalRailService/Reference.cs linguist-vendored
TrainDataAPI/Connected[[:space:]]Services/NationalRailDarwin/Reference.cs linguist-vendored
*.html linguist-vendored
6 changes: 5 additions & 1 deletion BusDataAPI/BusDeparture.cs
Original file line number Diff line number Diff line change
Expand Up @@ -12,9 +12,11 @@ public class BusDeparture
public string OperatorCode { get; set; }
public DateTime? AimedDeparture { get; set; }
public DateTime? ExpectedDeparture { get; set; }
public int TimeToStation { get; set; }
public string Platform { get; set; }
public bool IsCancelled { get; set; } = false;

public BusDeparture(string line, string destination, string stopName, string operatorCode, string operatorName, DateTime? aimedDeparture, DateTime? expectedDeparture)
public BusDeparture(string line, string destination, string stopName, string operatorCode, string operatorName, DateTime? aimedDeparture, DateTime? expectedDeparture, string platform = null, int? timeToStation = null)
{
LastUpdated = DateTime.Now;
Line = line;
Expand All @@ -24,6 +26,8 @@ public BusDeparture(string line, string destination, string stopName, string ope
OperatorName = operatorName;
AimedDeparture = aimedDeparture;
ExpectedDeparture = expectedDeparture;
Platform = platform;
TimeToStation = timeToStation ?? TimeSpan.FromTicks((expectedDeparture ?? DateTime.Now).Ticks - DateTime.Now.Ticks).Minutes;
}
}
}
72 changes: 15 additions & 57 deletions BusDataAPI/DataSource/TflApi.cs
Original file line number Diff line number Diff line change
@@ -1,17 +1,13 @@
using System;
using System.Collections.Generic;
using System.Linq;
using DepartureBoardCore;
using DepartureBoardCore.DataSource;
using Newtonsoft.Json;
using RestSharp;

namespace BusDataAPI.DataSource
{
public class TflApi : IBusDatasource
public class TflApi : TflBase, IBusDatasource
{
private readonly RestClient _client = new RestClient("https://api.tfl.gov.uk");
private static string AppKey => ConfigService.TflApiToken;

public List<BusDeparture> GetLiveDepartures(string code)
{
return GetDepartures(code);
Expand All @@ -25,59 +21,21 @@ private List<BusDeparture> GetDepartures(string code)
var arrivals = JsonConvert.DeserializeObject<List<TflArrival>>(response.Content);

return arrivals
.Select(a => new BusDeparture(a.LineName, a.DestinationName, a.StationName, "TFL", "Transport for London", a.ExpectedArrival, a.ExpectedArrival))
.Select(a => new BusDeparture(
a.LineName,
a.DestinationName,
a.StationName,
"TFL",
"Transport for London",
a.ExpectedArrival,
a.ExpectedArrival,
a.PlatformName,
GetConvertedTimeToStation(a.TimeToStation)
)
)
.Where(a => !string.IsNullOrEmpty(a.Destination))
.OrderBy(a => a.AimedDeparture)
.ToList();
}

// public List<StationLookup.Station> GetAllStations()
// {
// var request = new RestRequest($"StopPoint/mode/tube", Method.Get);
// RestResponse response = SendRequest(request);
//
// var stations = JsonConvert.DeserializeObject<TflStopPointResponse>(response.Content);
//
// return stations.StopPoints.
// Select(s => new StationLookup.Station(s.StationNaptan ?? s.NaptanId, s.CommonName, "GB", DataSourceId))
// .Distinct(new StationDeduplicator())
// .ToList();
// }

// private class StationDeduplicator : IEqualityComparer<StationLookup.Station>
// {
// public bool Equals(StationLookup.Station x, StationLookup.Station y)
// {
// return x?.Code == y?.Code;
// }
//
// public int GetHashCode(StationLookup.Station obj) => obj.Code.GetHashCode();
// }

private RestResponse SendRequest(RestRequest request)
{
request.AddQueryParameter("app_key", AppKey);
return _client.Execute(request);
}

public class TflStopPointResponse
{
public List<StopPoint> StopPoints { get; set; }
}

public class StopPoint
{
public string NaptanId { get; set; }
public string StationNaptan { get; set; }
public string CommonName { get; set; }
}

public class TflArrival
{
public string StationName { get; set; }
public string LineName { get; set; }
public string DestinationName { get; set; }
public DateTime ExpectedArrival { get; set; }
public string PlatformName { get; set; }
}
}
}
78 changes: 78 additions & 0 deletions DepartureBoardCore/DataSource/TflBase.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text.Json;
using RestSharp;

namespace DepartureBoardCore.DataSource;

public class TflBase
{
protected readonly RestClient _client = new RestClient("https://api.tfl.gov.uk");
protected static string AppKey => ConfigService.TflApiToken;
protected JsonSerializerOptions SerializerOptions = new JsonSerializerOptions { PropertyNamingPolicy = JsonNamingPolicy.CamelCase };
protected RestResponse SendRequest(RestRequest request)
{
request.AddQueryParameter("app_key", AppKey);
return _client.Execute(request);
}

protected int? GetConvertedTimeToStation(int? timeInSeconds)
{
if (!timeInSeconds.HasValue)
return null;

return Convert.ToInt32(Math.Ceiling(timeInSeconds.Value / 60m));
}

public class TflStopPointResponse
{
public List<StopPoint> StopPoints { get; set; }
}

public class StopPoint
{
public string NaptanId { get; set; }
public string StopType { get; set; }
public string StationNaptan { get; set; }
public string CommonName { get; set; }
public string PlatformName { get; set; }
public List<string> Modes { get; set; }
public List<Line> Lines { get; set; }
public List<LineModeGroup> LineModeGroups { get; set; }
public List<StopPoint> Children { get; set; }

public class Line
{
public string Id { get; set; }
public string Name { get; set; }
}

public class LineModeGroup
{
public string ModeName { get; set; }
public List<string> LineIdentifier { get; set; }
}

public bool IsHubStation()
{
return StopType == "TransportInterchange";
}

public List<StopPoint> GetChildTubeStopPoints()
{
return Children?.Where(c => c.Modes.Contains("tube")).ToList() ?? new List<StopPoint>();
}
}

public class TflArrival
{
public string StationName { get; set; }
public string LineId { get; set; }
public string LineName { get; set; }
public string DestinationName { get; set; }
public DateTime ExpectedArrival { get; set; }
public string PlatformName { get; set; }
public int? TimeToStation { get; set; }
}
}
4 changes: 4 additions & 0 deletions DepartureBoardCore/DepartureBoardCore.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -4,4 +4,8 @@
<TargetFramework>net6.0</TargetFramework>
</PropertyGroup>

<ItemGroup>
<PackageReference Include="RestSharp" Version="110.2.0" />
</ItemGroup>

</Project>
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ <h4 class="led">How to Use</h4>
<h4 class="led">How to Create the Json file</h4>
<ul>
<li>To start with I have created an example file that you can use as a template. <strong>You can download it at <a href="/files/examples/example-departures.json" download="example-departures.json">Here</a></strong></li>
<li>If you need help creating the file then i recommend using <a href="https://www.jsonschemavalidator.net/" target="_blank">https://www.jsonschemavalidator.net</a> and Paste <a href="/Schemas/departure.schema.json" target="_blank">this</a> into the schema section
<li>If you need help creating the file then i recommend using <a href="https://www.jsonschemavalidator.net/" target="_blank">https://www.jsonschemavalidator.net</a> and Paste <a href="assets/Schemas/departure.schema.json" target="_blank">this</a> into the schema section
<br>And then copy the example file above into the Input JSON section. You can then modify the file and it will tell you where the errors are.</li>
<li>For the system to read the dates they must be provided in this exact format. <strong>'yyyy-MM-ddThh:mm:ss'</strong>. E.g 2020-05-15T21:23:00 <br>Once the date has passed then the departure will no longer be shown on the board. You will need to update the date in order to show it again.</li>
<li>
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
#search-title {
padding-bottom: 10px;
}

.container {
border: 2px solid var(--mainColour);
border-radius: 8px;
color: var(--mainColour);
padding: 20px;
margin-top: 5px;
}

#btnSearch {
background-color: var(--mainColour);
color: black;
margin-top: 5px;
}

#search-box {
height: 90px;
}

form,
mat-form-field,
button {
width: 100%;
}

/* input color class */
::ng-deep input.mat-input-element {
color: var(--mainColour);
}

/* Change label color on focused */
::ng-deep .mat-form-field.mat-focused .mat-form-field-label {
color: var(--mainColour) !important;
}

/* underline border color on focused */
::ng-deep .mat-focused .mat-form-field-underline .mat-form-field-ripple {
background-color: var(--mainColour) !important;
}

.options mat-checkbox {
padding-right: 10px;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
<div class="container">
<h1 id="search-title" class="led centre-text clickable" style="padding-left: 10px" [routerLink]="['/search']">
Tube Search
</h1>
<form class="example-form">
<mat-form-field id="search-box">
<mat-icon matIconPrefix style="padding-bottom: 30px">search</mat-icon>
<mat-label>Station Name</mat-label>
<input id="search-input-box" type="text" placeholder="Ex. Baker" aria-label="station name" matInput
[formControl]="searchBox"
[matAutocomplete]="auto" (keyup)="keyPressed()" />
<mat-autocomplete autoActiveFirstOption #auto="matAutocomplete" [displayWith]="stationDisplay">
<mat-option *ngFor="let station of filteredOptions" [value]="station">
{{ station.name }}
</mat-option>
</mat-autocomplete>
</mat-form-field>

<div class="options" *ngIf="showSubSearchParams">
<mat-label>Optional filters</mat-label>
<mat-form-field>
<mat-label>Line</mat-label>
<mat-select [formControl]="lineFilter">
<mat-option *ngFor="let line of stationInfo?.lines" [value]="line">
{{line.name}}
</mat-option>
</mat-select>
</mat-form-field>

<!-- <mat-form-field>-->
<!-- <mat-label>Direction</mat-label>-->
<!-- <mat-select>-->
<!-- <mat-option *ngFor="let line of stationInfo?.lines" [value]="line">-->
<!-- {{line.name}}-->
<!-- </mat-option>-->
<!-- </mat-select>-->
<!-- </mat-form-field>-->
</div>

<button mat-button id="btnSearch" (click)="Search()">Search</button>
</form>
</div>
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
import { ComponentFixture, TestBed } from '@angular/core/testing';

import { LondonTubeSearchComponent } from './london-tube-search.component';

describe('LondonTubeSearchComponent', () => {
let component: LondonTubeSearchComponent;
let fixture: ComponentFixture<LondonTubeSearchComponent>;

beforeEach(async () => {
await TestBed.configureTestingModule({
declarations: [ LondonTubeSearchComponent ]
})
.compileComponents();

fixture = TestBed.createComponent(LondonTubeSearchComponent);
component = fixture.componentInstance;
fixture.detectChanges();
});

it('should create', () => {
expect(component).toBeTruthy();
});
});
Loading

0 comments on commit 518a393

Please sign in to comment.