program app1;
{$mode objfpc}{$H+}
{$modeswitch advancedrecords}
{$modeswitch typehelpers}
uses
Classes,
SysUtils,
DateUtils,
lgUtils,
lgHashMap,
lgVector,
lgJson,
lgCsvUtils,
fphttpapp,
httpdefs,
httproute;
type
TStopTime = record
stop_id,
arrival_time,
departure_time: string;
constructor Make(const aStopID, aArrival, aDeparture: string);
procedure WriteJson(aWriter: TJsonWriter);
end;
TStopTimeList = specialize TGVector<TStopTime>;
TStopTimeMap = specialize TGObjHashMapLP<string, TStopTimeList>;
TTrip = record
trip_id,
service_id,
route_id: string;
constructor Make(const aTripID, aRouteID, aServiceID: string);
end;
TTripList = specialize TGVector<TTrip>;
TTripMap = specialize TGObjHashMapLP<string, TTripList>;
TTripResponse = record
trip_id,
service_id,
route_id: string;
schedules: TStopTimeList;
constructor Make(const aTrip: TTrip);
procedure WriteJson(aWriter: TJsonWriter);
end;
TTripResponses = array of TTripResponse;
TTrHelper = type helper for TTripResponses
procedure WriteJson(aWriter: TJsonWriter);
end;
{ TStopTime }
constructor TStopTime.Make(const aStopID, aArrival, aDeparture: string);
begin
stop_id := aStopID;
arrival_time := aArrival;
departure_time := aDeparture;
end;
procedure TStopTime.WriteJson(aWriter: TJsonWriter);
begin
aWriter
.BeginObject
.Add('stop_id', stop_id)
.Add('arrival_time', arrival_time)
.Add('departure_time', departure_time)
.EndObject;
end;
{ TTrip }
constructor TTrip.Make(const aTripID, aRouteID, aServiceID: string);
begin
trip_id := aTripID;
route_id := aRouteID;
service_id := aServiceID;
end;
{ TTripResponse }
constructor TTripResponse.Make(const aTrip: TTrip);
begin
trip_id := aTrip.trip_id;
service_id := aTrip.service_id;
route_id := aTrip.route_id;
schedules := nil;
end;
procedure TTripResponse.WriteJson(aWriter: TJsonWriter);
var
I: SizeInt;
begin
aWriter.BeginObject
.Add('trip_id', trip_id)
.Add('service_id', service_id)
.Add('route_id', route_id)
.AddName('schedules')
.BeginArray;
if schedules <> nil then
for I := 0 to Pred(schedules.Count) do
schedules.UncMutable[I]^.WriteJson(aWriter);
aWriter.EndArray;
aWriter.EndObject;
end;
{ TTrHelper }
procedure TTrHelper.WriteJson(aWriter: TJsonWriter);
var
I: SizeInt;
begin
aWriter.BeginArray;
for I := 0 to High(Self) do
Self[I].WriteJson(aWriter);
aWriter.EndArray;
end;
function BuildTripResponse(const ARoute: string; const AStopTimesIxByTrip: TStopTimeMap;
const ATripsIxByRoute: TTripMap): TTripResponses;
var
LTrips: TTripList;
LStopTimes: TStopTimeList;
LTripIx: Integer;
LTripResponse: TTripResponse;
begin
Result := nil;
if ATripsIxByRoute.TryGetValue(ARoute, LTrips) then begin
SetLength(Result, LTrips.Count);
for LTripIx := 0 to Pred(LTrips.Count) do begin
LTripResponse := TTripResponse.Make(LTrips.UncMutable[LTripIx]^);
if AStopTimesIxByTrip.TryGetValue(LTripResponse.trip_id, LStopTimes) then
LTripResponse.schedules := LStopTimes;
Result[LTripIx] := LTripResponse;
end;
end;
end;
procedure GetStopTimes(var AStopTimesIxByTrip: TStopTimeMap);
var
CsvRef: specialize TGAutoRef<TCsvDoc>;
LCSV: TCsvDoc;
LStart,LEnd: TDateTime;
i: Integer;
LStopTimesIx: ^TStopTimeList;
p: TCsvDoc.PRow;
begin
LStart := Now;
LCSV := CsvRef.Instance.LoadFromFile('../MBTA_GTFS/stop_times.txt', [true, true, true, true]);
if (LCSV[0, 0] <> 'trip_id') or (LCSV[0, 3] <> 'stop_id') or (LCSV[0, 1] <> 'arrival_time') or (LCSV[0, 2] <> 'departure_time') then begin
WriteLn('stop_times.txt not in expected format:');
for i := 0 to LCSV.ColCount[0] - 1 do begin
WriteLn(i, ' ' + LCSV[0, i]);
end;
Halt(1);
end;
AStopTimesIxByTrip := TStopTimeMap.Create([moOwnsValues]);
for i := 1 to LCSV.RowCount - 1 do begin
p := LCSV.Rows[i];
if not AStopTimesIxByTrip.FindOrAddMutValue(p^[0], LStopTimesIx) then
LStopTimesIx^ := TStopTimeList.Create;
LStopTimesIx^.Add(TStopTime.Make(p^[3], p^[1], p^[2]));
end;
LEnd := Now;
WriteLn('parsed ', LCSV.RowCount, ' stop times in ', SecondSpan(LStart, LEnd):1:3,' seconds');
end;
procedure GetTrips(var ATripsIxByRoute: TTripMap);
var
CsvRef: specialize TGAutoRef<TCsvDoc>;
LCSV: TCsvDoc;
LStart,LEnd: TDateTime;
i: Integer;
LTripsIx: ^TTripList;
LRoute: string;
p: TCsvDoc.PRow;
begin
LStart := Now;
LCSV := CsvRef.Instance.LoadFromFile('../MBTA_GTFS/trips.txt', [true, true, true]);
if (LCSV[0, 2] <> 'trip_id') or (LCSV[0, 0] <> 'route_id') or (LCSV[0, 1] <> 'service_id') then begin
WriteLn('trips.txt not in expected format:');
for i := 0 to LCSV.ColCount[0] - 1 do begin
WriteLn(i, ' ' + LCSV[0, 1]);
end;
Halt(1);
end;
ATripsIxByRoute := TTripMap.Create([moOwnsValues]);
for i := 1 to LCSV.RowCount - 1 do begin
p := LCSV.Rows[i];
LRoute := p^[0];
if not ATripsIxByRoute.FindOrAddMutValue(LRoute, LTripsIx) then
LTripsIx^ := TTripList.Create;
LTripsIx^.Add(TTrip.Make(p^[2], LRoute, p^[1]));
end;
LEnd := Now;
WriteLn('parsed ', LCSV.RowCount, ' trips in ', SecondSpan(LStart, LEnd):1:3,' seconds');
end;
var
GStopTimesIxByTrip: TStopTimeMap = nil;
GTripsIxByRoute: TTripMap = nil;
procedure SchedulesHandler(ARequest: TRequest; AResponse: TResponse);
var
LTripResponses: TTripResponses;
LStringStream: specialize TGAutoRef<TStringStream>;
LWriter: specialize TGUniqRef<TJsonWriter>;
begin
LTripResponses := BuildTripResponse(ARequest.RouteParams['route'], GStopTimesIxByTrip, GTripsIxByRoute);
{%H-}LWriter.Instance := TJsonWriter.New(LStringStream.Instance);
LTripResponses.WriteJson(LWriter.Instance);
LWriter.Clear;
AResponse.ContentType := 'application/json';
AResponse.Content := LStringStream.Instance.DataString;
end;
procedure StopHandler(ARequest: TRequest; AResponse: TResponse);
begin
GStopTimesIxByTrip.Free;
GTripsIxByRoute.Free;
Application.Terminate;
end;
begin
GetStopTimes(GStopTimesIxByTrip);
GetTrips(GTripsIxByRoute);
Application.Port := 4000;
Application.Threaded := True;
HTTPRouter.RegisterRoute('/schedules/:route',@SchedulesHandler);
HTTPRouter.RegisterRoute('/stop', @StopHandler);
Application.Run;
end.