Skip to content

Commit

Permalink
Merge pull request #8 from SchweizerischeBundesbahnen/feature/gtfs-st…
Browse files Browse the repository at this point in the history
…atic

Feature/gtfs static
  • Loading branch information
munterfi authored Nov 29, 2024
2 parents d9ad9e7 + 8abd446 commit 07774da
Show file tree
Hide file tree
Showing 30 changed files with 1,408 additions and 659 deletions.
11 changes: 6 additions & 5 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,8 +1,9 @@
# Netzgrafik-Editor MATSim Converter
# Netzgrafik-Editor Converter

This tool converts network graphics from
the [Netzgrafik-Editor](https://github.com/SchweizerischeBundesbahnen/netzgrafik-editor-frontend) into MATSim transit
schedules.
Converter to expand network graphics from
the [Netzgrafik-Editor](https://github.com/SchweizerischeBundesbahnen/netzgrafik-editor-frontend) into timetables for
the entire service
day in different formats, as for example GTFS static or MATSim transit schedules.

## Design

Expand All @@ -14,7 +15,7 @@ The converter has a modular design (DI):
- **supply**: A generic supply builder interface. Defines infrastructure and rolling stock repositories, as well
as vehicle circuits planner interfaces used in the builder.
- **validation**: Network graphic ID validator and sanitizer.
- **matsim**: MATSim-specific transit schedule builder, implementing the supply builder interface.
- **adapter**: Format-specific transit schedule builder, implementing the supply builder interface.
- **io**: Provides implementations for network graphic sources and converter output sinks.

The class diagram outlines the core classes and their relationships:
Expand Down
54 changes: 39 additions & 15 deletions docs/uml/class-diagram.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ set namespaceSeparator none
top to bottom direction
package netzgrafikeditor.converter.core {
class NetzgrafikConverter {
class NetzgrafikConverter<T> {
+run()
}
Expand All @@ -21,18 +21,24 @@ package netzgrafikeditor.converter.core {
+load()
}
interface ConverterSink {
+save()
interface ConverterSink<T> {
+save(...)
}
package supply {
interface SupplyBuilder {
interface SupplyBuilder<T> {
+addStopFacility(...)
+addTransitLine(...)
+addRouteStop(...)
+addRoutePass(...)
+addDeparture(...)
+build()
+build(): T
}
abstract class BaseSupplyBuilder<T> {
#{abstract}buildStopFacility(...)
#{abstract}buildTransitRoute(...)
#{abstract}buildDeparture(...)
}
interface InfrastructureRepository {
Expand All @@ -51,27 +57,45 @@ package netzgrafikeditor.converter.core {
}
}
package matsim {
package adapter {
class MatsimSupplyBuilder {
}
class GtfsSupplyBuilder {
}
}
package io.matsim {
package io {
class GtfsScheduleWriter {
}
class TransitScheduleXmlWriter {
}
class JsonFileReader {
}
}
NetzgrafikConverter *- NetzgrafikConverterConfig : has
NetzgrafikConverter *-- NetworkGraphicSource : has
NetzgrafikConverter *--- SupplyBuilder : has
NetzgrafikConverter *-- ConverterSink : has
NetzgrafikConverter .> MatsimSupplyBuilder : <<runtime>>
NetzgrafikConverter .> TransitScheduleXmlWriter : <<runtime>>
MatsimSupplyBuilder .|> SupplyBuilder: <<implements>>
TransitScheduleXmlWriter ..|> ConverterSink: <<implements>>
MatsimSupplyBuilder *-- InfrastructureRepository : has
MatsimSupplyBuilder *-- RollingStockRepository : has
MatsimSupplyBuilder *-- VehicleCircuitsPlanner : has
BaseSupplyBuilder .|> SupplyBuilder: <<implements>>
BaseSupplyBuilder *-- InfrastructureRepository : has
BaseSupplyBuilder *-- RollingStockRepository : has
BaseSupplyBuilder *-- VehicleCircuitsPlanner : has
MatsimSupplyBuilder --|> BaseSupplyBuilder : <<extends>>
note on link: T = Scenario
GtfsSupplyBuilder -|> BaseSupplyBuilder : <<extends>>
note on link: T = GtfsSchedule
GtfsScheduleWriter ..|> ConverterSink: <<implements>>
note on link: T = GtfsSchedule
TransitScheduleXmlWriter .|> ConverterSink: <<implements>>
note on link: T = Scenario
JsonFileReader .|> NetworkGraphicSource: <<implements>>
@enduml
```
526 changes: 324 additions & 202 deletions docs/uml/class-diagram.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
7 changes: 4 additions & 3 deletions pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -9,11 +9,12 @@
<relativePath/> <!-- lookup parent from repository -->
</parent>
<groupId>ch.sbb.pfi.netzgrafikeditor</groupId>
<artifactId>netzgrafik-editor-matsim-converter</artifactId>
<artifactId>netzgrafik-editor-converter</artifactId>
<version>2.7.0-SNAPSHOT</version>
<name>netzgrafik-editor-matsim-converter</name>
<description>Netzgrafik-Editor MATSim Converter</description>

<description>Converter to expand network graphics from the Netzgrafik-Editor into timetables for the entire service
day in different formats
</description>
<properties>
<matsim.version>2025.0-2024w37</matsim.version>
<java.version>21</java.version>
Expand Down
17 changes: 7 additions & 10 deletions src/main/java/ch/sbb/pfi/netzgrafikeditor/Application.java
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
package ch.sbb.pfi.netzgrafikeditor;

import ch.sbb.pfi.netzgrafikeditor.converter.adapter.matsim.MatsimSupplyBuilder;
import ch.sbb.pfi.netzgrafikeditor.converter.core.ConverterSink;
import ch.sbb.pfi.netzgrafikeditor.converter.core.NetworkGraphicConverter;
import ch.sbb.pfi.netzgrafikeditor.converter.core.NetworkGraphicConverterConfig;
Expand All @@ -11,10 +12,7 @@
import ch.sbb.pfi.netzgrafikeditor.converter.core.validation.ValidationStrategy;
import ch.sbb.pfi.netzgrafikeditor.converter.io.matsim.TransitScheduleXmlWriter;
import ch.sbb.pfi.netzgrafikeditor.converter.io.netzgrafik.JsonFileReader;
import ch.sbb.pfi.netzgrafikeditor.converter.matsim.MatsimSupplyBuilder;
import org.matsim.api.core.v01.Scenario;
import org.matsim.core.config.ConfigUtils;
import org.matsim.core.scenario.ScenarioUtils;
import org.springframework.boot.CommandLineRunner;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
Expand All @@ -33,7 +31,7 @@ public static void main(String[] args) {
}

@Override
public void run(String... args) throws Exception {
public void run(String... args) {
if (args.length < 2) {
System.err.println("Please provide the path to the network graphic JSON file and the output directory.");
System.exit(1);
Expand All @@ -52,8 +50,7 @@ private void convertJsonToMatsimSchedule(Path jsonFilePath) {
.validationStrategy(ValidationStrategy.WARN_ON_ISSUES)
.build();

Scenario scenario = ScenarioUtils.createScenario(ConfigUtils.createConfig());
NetworkGraphicConverter converter = getNetworkGraphicConverter(jsonFilePath, scenario, config);
NetworkGraphicConverter<Scenario> converter = getNetworkGraphicConverter(jsonFilePath, config);

converter.run();

Expand All @@ -65,18 +62,18 @@ private void convertJsonToMatsimSchedule(Path jsonFilePath) {
}
}

private NetworkGraphicConverter getNetworkGraphicConverter(Path jsonFilePath, Scenario scenario, NetworkGraphicConverterConfig config) {
private NetworkGraphicConverter<Scenario> getNetworkGraphicConverter(Path jsonFilePath, NetworkGraphicConverterConfig config) {
NetworkGraphicSource source = new JsonFileReader(jsonFilePath);

SupplyBuilder builder = new MatsimSupplyBuilder(scenario, new NoInfrastructureRepository(),
SupplyBuilder<Scenario> builder = new MatsimSupplyBuilder(new NoInfrastructureRepository(),
new NoVehicleCircuitsPlanner(new NoRollingStockRepository()));

String baseFilename = jsonFilePath.getFileName().toString();
String filenameWithoutExtension = jsonFilePath.getFileName()
.toString()
.substring(0, baseFilename.lastIndexOf('.'));
ConverterSink sink = new TransitScheduleXmlWriter(scenario, outputPath, filenameWithoutExtension + ".");
ConverterSink<Scenario> sink = new TransitScheduleXmlWriter(outputPath, filenameWithoutExtension + ".");

return new NetworkGraphicConverter(config, source, builder, sink);
return new NetworkGraphicConverter<>(config, source, builder, sink);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,154 @@
package ch.sbb.pfi.netzgrafikeditor.converter.adapter.gtfs;

import ch.sbb.pfi.netzgrafikeditor.converter.adapter.gtfs.model.Agency;
import ch.sbb.pfi.netzgrafikeditor.converter.adapter.gtfs.model.Calendar;
import ch.sbb.pfi.netzgrafikeditor.converter.adapter.gtfs.model.GtfsSchedule;
import ch.sbb.pfi.netzgrafikeditor.converter.adapter.gtfs.model.Route;
import ch.sbb.pfi.netzgrafikeditor.converter.adapter.gtfs.model.Stop;
import ch.sbb.pfi.netzgrafikeditor.converter.adapter.gtfs.model.StopTime;
import ch.sbb.pfi.netzgrafikeditor.converter.adapter.gtfs.model.Trip;
import ch.sbb.pfi.netzgrafikeditor.converter.core.supply.BaseSupplyBuilder;
import ch.sbb.pfi.netzgrafikeditor.converter.core.supply.InfrastructureRepository;
import ch.sbb.pfi.netzgrafikeditor.converter.core.supply.RouteElement;
import ch.sbb.pfi.netzgrafikeditor.converter.core.supply.RouteElementVisitor;
import ch.sbb.pfi.netzgrafikeditor.converter.core.supply.RoutePass;
import ch.sbb.pfi.netzgrafikeditor.converter.core.supply.RouteStop;
import ch.sbb.pfi.netzgrafikeditor.converter.core.supply.StopFacilityInfo;
import ch.sbb.pfi.netzgrafikeditor.converter.core.supply.VehicleAllocation;
import ch.sbb.pfi.netzgrafikeditor.converter.core.supply.VehicleCircuitsPlanner;

import java.time.Duration;
import java.time.LocalDate;
import java.time.LocalTime;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;

public class GtfsSupplyBuilder extends BaseSupplyBuilder<GtfsSchedule> {

public static final int ROUTE_TYPE = 0;
private static final Agency AGENCY = Agency.builder()
.agencyId("nge")
.agencyName("Netzgrafik Editor")
.agencyTimeZone("UTC")
.agencyUrl("https://github.com/SchweizerischeBundesbahnen/netzgrafik-editor-frontend")
.build();
private static final Calendar CALENDAR = Calendar.builder()
.serviceId("always")
.monday(Calendar.Type.AVAILABLE)
.tuesday(Calendar.Type.AVAILABLE)
.wednesday(Calendar.Type.AVAILABLE)
.thursday(Calendar.Type.AVAILABLE)
.friday(Calendar.Type.AVAILABLE)
.startDate(LocalDate.MAX)
.endDate(LocalDate.MAX)
.build();
private final List<Stop> stops = new ArrayList<>();
private final List<Route> routes = new ArrayList<>();
private final List<Trip> trips = new ArrayList<>();
private final List<StopTime> stopTimes = new ArrayList<>();

private final Set<String> createdRoutes = new HashSet<>();
private final Map<String, List<RouteElement>> routeElements = new HashMap<>();

public GtfsSupplyBuilder(InfrastructureRepository infrastructureRepository, VehicleCircuitsPlanner vehicleCircuitsPlanner) {
super(infrastructureRepository, vehicleCircuitsPlanner);
}

@Override
protected void buildStopFacility(StopFacilityInfo stopFacilityInfo) {
stops.add(Stop.builder()
.stopId(stopFacilityInfo.getId())
.stopName(stopFacilityInfo.getId())
.stopLat(stopFacilityInfo.getCoordinate().getLatitude())
.stopLon(stopFacilityInfo.getCoordinate().getLongitude())
.build());
}

@Override
protected void buildTransitRoute(TransitRouteContainer transitRouteContainer) {

// store route elements for stop time creation
routeElements.put(transitRouteContainer.transitRouteInfo().getId(), transitRouteContainer.routeElements());

// create and add GTFS route (transit line in the context of the supply builder) if not yet added
String routeId = transitRouteContainer.transitRouteInfo().getTransitLineInfo().getId();
if (!createdRoutes.contains(routeId)) {
routes.add(Route.builder()
.routeId(routeId)
.agencyId(AGENCY.getAgencyId())
.routeLongName(routeId)
.routeShortName(routeId)
.routeType(ROUTE_TYPE)
.build());
createdRoutes.add(routeId);
}

// create and add trip
trips.add(Trip.builder()
.routeId(routeId)
.serviceId(CALENDAR.getServiceId())
.tripId(transitRouteContainer.transitRouteInfo().getId())
.tripHeadsign(transitRouteContainer.routeElements().getLast().getStopFacilityInfo().getId())
.build());
}

@Override
protected void buildDeparture(VehicleAllocation vehicleAllocation) {

String tripId = vehicleAllocation.getDepartureInfo().getTransitRouteInfo().getId();
final LocalTime[] time = {vehicleAllocation.getDepartureInfo().getTime()};
final int[] count = {0};

for (RouteElement routeElement : routeElements.get(tripId)) {
routeElement.accept(new RouteElementVisitor() {

@Override
public void visit(RouteStop routeStop) {
Duration travelTime = routeStop.getTravelTime();
Duration dwellTime = routeStop.getDwellTime();

// set time to arrival time if at start of stop time sequence
if (count[0] == 0) {
time[0] = time[0].minus(dwellTime);
}

time[0] = time[0].plus(travelTime);
LocalTime arrivalTime = time[0];
time[0] = time[0].plus(dwellTime);
LocalTime departureTime = time[0];

stopTimes.add(StopTime.builder()
.tripId(tripId)
.arrivalTime(arrivalTime)
.departureTime(departureTime)
.stopId(routeStop.getStopFacilityInfo().getId())
.stopSequence(count[0]++)
.build());
}

@Override
public void visit(RoutePass routePass) {
// nothing to do
}
});

}

}

@Override
protected GtfsSchedule getResult() {
return GtfsSchedule.builder()
.agencies(List.of(AGENCY))
.stops(stops)
.routes(routes)
.trips(trips)
.stopTimes(stopTimes)
.calendars(List.of(CALENDAR))
.build();
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
package ch.sbb.pfi.netzgrafikeditor.converter.adapter.gtfs.model;

import lombok.Builder;
import lombok.Value;

@Value
@Builder
public class Agency {

String agencyId;

String agencyName;

String agencyUrl;

String agencyTimeZone;

}
Loading

0 comments on commit 07774da

Please sign in to comment.