Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Feature/default converter #1

Merged
merged 11 commits into from
Nov 5, 2024
18 changes: 9 additions & 9 deletions .github/workflows/maven-ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -11,15 +11,15 @@ jobs:
runs-on: ubuntu-latest

steps:
- uses: actions/checkout@v4
- name: Set up JDK 21
uses: actions/setup-java@v4
with:
java-version: '21'
distribution: 'temurin'
cache: maven
- name: Build with Maven
run: mvn -B package --file pom.xml
- uses: actions/checkout@v4
- name: Set up JDK 21
uses: actions/setup-java@v4
with:
java-version: '21'
distribution: 'temurin'
cache: maven
- name: Build with Maven
run: mvn -B package --file pom.xml

# Optional: Uploads the full dependency graph to GitHub to improve the quality of Dependabot alerts this repository can receive
# - name: Update dependency graph
Expand Down
5 changes: 4 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -35,4 +35,7 @@ build/
.vscode/

### Mac OS ###
.DS_Store
.DS_Store

### Integrations Tests ###
integration-test/output/
70 changes: 70 additions & 0 deletions src/main/java/ch/sbb/pfi/netzgrafikeditor/Application.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
package ch.sbb.pfi.netzgrafikeditor;

import ch.sbb.pfi.netzgrafikeditor.converter.ConverterSink;
import ch.sbb.pfi.netzgrafikeditor.converter.NetworkGraphicSource;
import ch.sbb.pfi.netzgrafikeditor.converter.NetzgrafikConverter;
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 ch.sbb.pfi.netzgrafikeditor.converter.supply.SupplyBuilder;
import ch.sbb.pfi.netzgrafikeditor.converter.supply.impl.NoInfrastructureRepository;
import ch.sbb.pfi.netzgrafikeditor.converter.supply.impl.NoRollingStockRepository;
import ch.sbb.pfi.netzgrafikeditor.converter.supply.impl.NoVehicleCircuitsPlanner;
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;

import java.io.IOException;
import java.nio.file.Path;
import java.nio.file.Paths;

@SpringBootApplication
public class Application implements CommandLineRunner {

private Path outputPath;

public static void main(String[] args) {
SpringApplication.run(Application.class, args);
}

@Override
public void run(String... args) throws Exception {
if (args.length < 2) {
System.err.println("Please provide the path to the netzgrafik JSON file and the output directory.");
System.exit(1);
}

String jsonFilePath = args[0];
String outputDirPath = args[1];
outputPath = Paths.get(outputDirPath);

convertJsonToMatsimSchedule(Paths.get(jsonFilePath));
}

private void convertJsonToMatsimSchedule(Path jsonFilePath) {
try {
Scenario scenario = ScenarioUtils.createScenario(ConfigUtils.createConfig());
NetworkGraphicSource source = new JsonFileReader(jsonFilePath);
SupplyBuilder builder = new MatsimSupplyBuilder(scenario, new NoInfrastructureRepository(source.load()),
new NoRollingStockRepository(), new NoVehicleCircuitsPlanner());

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

NetzgrafikConverter converter = new NetzgrafikConverter(source, builder, sink);
converter.run();

System.out.println("MATSim schedule has been written to: " + outputPath);
System.exit(0);
} catch (IOException e) {
System.err.println("Error during conversion: " + e.getMessage());
System.exit(1);
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
package ch.sbb.pfi.netzgrafikeditor.converter;

import java.io.IOException;

public interface ConverterSink {

void save() throws IOException;

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
package ch.sbb.pfi.netzgrafikeditor.converter;

import ch.sbb.pfi.netzgrafikeditor.converter.model.NetworkGraphic;

import java.io.IOException;

public interface NetworkGraphicSource {

NetworkGraphic load() throws IOException;

}
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,6 @@
import lombok.extern.slf4j.Slf4j;

import java.io.IOException;
import java.nio.file.Path;
import java.time.Duration;
import java.time.LocalTime;
import java.util.ArrayList;
Expand All @@ -36,7 +35,9 @@ public class NetzgrafikConverter {
private static final double OPERATION_DAY_START_TIME_SECONDS = 2 * 3600.;
private static final double OPERATION_DAY_END_TIME_SECONDS = 23 * 3600.;

private final NetworkGraphicSource source;
private final SupplyBuilder supplyBuilder;
private final ConverterSink sink;

private Map<String, Integer> lineCounter;
private Lookup lookup;
Expand Down Expand Up @@ -72,19 +73,21 @@ private static Duration getDwellTimeFromCategory(Node node, String fachCategory)
return Duration.ofSeconds(Math.round(trainrunCategoryHaltezeit.getHaltezeit() * 60));
}

void read(Path filePath) throws IOException {
log.info("Converting netzgrafik using {}", supplyBuilder.getClass().getSimpleName());
NetworkGraphic network = new JsonDeserializer().read(filePath);
public void run() throws IOException {
log.info("Converting netzgrafik using source {}, supply builder {} and sink {}",
source.getClass().getSimpleName(), supplyBuilder.getClass().getSimpleName(),
sink.getClass().getSimpleName());

// remove spaces and dots
// TODO: Validate input in deserializer?
// network.getNodes().forEach(n -> n.set(n.getBetriebspunktName().replaceAll(" ", "_").replaceAll("\\.", "")));
initialize(source.load());
addStops();
addTrains();

sink.save();
}

private void initialize(NetworkGraphic network) {
lineCounter = new HashMap<>();
lookup = new Lookup(network);

addStops();
addTrains();
}

/**
Expand Down Expand Up @@ -199,8 +202,10 @@ private void createAndAddTransitLine(Trainrun train, List<Node> nodes, List<Trai
List<DayTimeInterval> timeIntervals = lookup.times.get(train.getTrainrunTimeCategoryId()).getDayTimeIntervals();
if (timeIntervals.isEmpty()) {
// add interval for full day (in minutes)
timeIntervals.add(DayTimeInterval.builder().from((int) (Math.round(OPERATION_DAY_START_TIME_SECONDS / 60.)))
.to(((int) Math.round(OPERATION_DAY_END_TIME_SECONDS / 60.))).build());
timeIntervals.add(DayTimeInterval.builder()
.from((int) (Math.round(OPERATION_DAY_START_TIME_SECONDS / 60.)))
.to(((int) Math.round(OPERATION_DAY_END_TIME_SECONDS / 60.)))
.build());
}

// create departures in intervals for both directions
Expand Down Expand Up @@ -305,7 +310,10 @@ private static class Lookup {
this.nodes = listToHashMap(network.getNodes());
this.ports = listToHashMap(
network.getNodes().stream().map(Node::getPorts).flatMap(List::stream).collect(Collectors.toList()));
this.transitions = listToHashMap(network.getNodes().stream().map(Node::getTransitions).flatMap(List::stream)
this.transitions = listToHashMap(network.getNodes()
.stream()
.map(Node::getTransitions)
.flatMap(List::stream)
.collect(Collectors.toList()));
this.trains = listToHashMap(network.getTrainruns());
this.sections = listToHashMap(network.getTrainrunSections());
Expand All @@ -315,8 +323,8 @@ private static class Lookup {
}

private <T extends Identifiable> Map<Integer, T> listToHashMap(List<T> list) {
return list.stream().collect(
Collectors.toMap(Identifiable::getId, element -> element, (element, element2) -> element,
return list.stream()
.collect(Collectors.toMap(Identifiable::getId, element -> element, (element, element2) -> element,
HashMap::new));
}

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
package ch.sbb.pfi.netzgrafikeditor.converter.io.matsim;

import ch.sbb.pfi.netzgrafikeditor.converter.ConverterSink;
import lombok.RequiredArgsConstructor;
import org.matsim.api.core.v01.Scenario;
import org.matsim.core.config.ConfigWriter;
import org.matsim.core.network.io.NetworkWriter;
import org.matsim.pt.transitSchedule.api.TransitScheduleWriter;
import org.matsim.vehicles.MatsimVehicleWriter;

import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;

@RequiredArgsConstructor
public class TransitScheduleXmlWriter implements ConverterSink {

private static final String CONFIG_FILE = "config.xml";
private static final String NETWORK_FILE = "network.xml.gz";
private static final String TRANSIT_SCHEDULE_FILE = "transitSchedule.xml.gz";
private static final String TRANSIT_VEHICLE_FILE = "transitVehicles.xml.gz";

private final Scenario scenario;
private final Path directory;
private final String prefix;

@Override
public void save() throws IOException {
Files.createDirectories(directory);

writeFile(CONFIG_FILE, new ConfigWriter(scenario.getConfig())::write);
writeFile(NETWORK_FILE, new NetworkWriter(scenario.getNetwork())::write);
writeFile(TRANSIT_SCHEDULE_FILE, new TransitScheduleWriter(scenario.getTransitSchedule())::writeFile);
writeFile(TRANSIT_VEHICLE_FILE, new MatsimVehicleWriter(scenario.getTransitVehicles())::writeFile);
}

private void writeFile(String fileName, FileWriterAction writerAction) throws IOException {
Path filePath = directory.resolve(prefix + fileName);
writerAction.write(filePath.toString());
}

@FunctionalInterface
private interface FileWriterAction {
void write(String filePath) throws IOException;
}
}
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
package ch.sbb.pfi.netzgrafikeditor.converter;
package ch.sbb.pfi.netzgrafikeditor.converter.io.netzgrafik;

import ch.sbb.pfi.netzgrafikeditor.converter.model.NetworkGraphic;
import com.fasterxml.jackson.databind.ObjectMapper;
Expand All @@ -24,6 +24,10 @@ public class JsonDeserializer {
*/
public NetworkGraphic read(String jsonString) throws IOException {
log.info("Reading netzgrafik from JSON string");

// TODO: Validate input in deserializer? Remove spaces and dots?
// network.getNodes().forEach(n -> n.set(n.getBetriebspunktName().replaceAll(" ", "_").replaceAll("\\.", "")));

return objectMapper.readValue(jsonString, NetworkGraphic.class);
}

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
package ch.sbb.pfi.netzgrafikeditor.converter.io.netzgrafik;

import ch.sbb.pfi.netzgrafikeditor.converter.NetworkGraphicSource;
import ch.sbb.pfi.netzgrafikeditor.converter.model.NetworkGraphic;
import lombok.RequiredArgsConstructor;

import java.io.IOException;
import java.nio.file.Path;

@RequiredArgsConstructor
public class JsonFileReader implements NetworkGraphicSource {

private final Path filePath;
private volatile NetworkGraphic networkGraphic; // ensure visibility across threads

@Override
public NetworkGraphic load() throws IOException {

// first check (without locking)
if (networkGraphic == null) {
synchronized (this) {
// second check (with locking)
if (networkGraphic == null) {
networkGraphic = new JsonDeserializer().read(filePath);
}
}
}

return networkGraphic;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -43,8 +43,8 @@ class InfrastructureBuilder {
TransitStopFacility buildTransitStopFacility(StopFacilityInfo stopFacilityInfo) {
String stopId = stopFacilityInfo.getId();

Coord coord = new Coord(stopFacilityInfo.getCoordinate().getLatitude(),
stopFacilityInfo.getCoordinate().getLongitude());
Coord coord = new Coord(stopFacilityInfo.getCoordinate().getLongitude(),
stopFacilityInfo.getCoordinate().getLatitude());
Node node = factory.createNode(String.format("%s", stopId), coord);
Link stopLink = factory.createLink(LinkType.STOP, node, node, STOP_LINK_LENGTH,
stopFacilityInfo.getLinkAttributes());
Expand Down Expand Up @@ -122,7 +122,7 @@ private List<Id<Link>> connect(Map<String, TransitStopFacility> stopFacilities,
// check if segment was already added
if (addedSegments.containsKey(segment.getSegmentId())) {

log.info("Track segment {} already added, skipping", segment.getSegmentId());
log.debug("Track segment {} already added, skipping", segment.getSegmentId());
linkIds.add(addedSegments.get(segment.getSegmentId()));

} else { // segment is new, create link with nodes
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -68,7 +68,8 @@ public SupplyBuilder addTransitLine(String lineId, String vehicleTypeId, String

VehicleTypeInfo vehicleTypeInfo = vehicleTypeInfos.get(vehicleTypeId);
if (vehicleTypeInfo == null) {
vehicleTypeInfos.put(vehicleTypeId, rollingStockRepository.getVehicleType(vehicleTypeId));
vehicleTypeInfo = rollingStockRepository.getVehicleType(vehicleTypeId);
vehicleTypeInfos.put(vehicleTypeId, vehicleTypeInfo);
}

StopFacilityInfo originStop = stopFacilityInfos.get(originStopId);
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
package ch.sbb.pfi.netzgrafikeditor.converter.supply.impl;

import ch.sbb.pfi.netzgrafikeditor.converter.model.NetworkGraphic;
import ch.sbb.pfi.netzgrafikeditor.converter.model.Node;
import ch.sbb.pfi.netzgrafikeditor.converter.supply.Coordinate;
import ch.sbb.pfi.netzgrafikeditor.converter.supply.InfrastructureRepository;
import ch.sbb.pfi.netzgrafikeditor.converter.supply.StopFacilityInfo;
import ch.sbb.pfi.netzgrafikeditor.converter.supply.TrackSegmentInfo;
import ch.sbb.pfi.netzgrafikeditor.converter.supply.TransitLineInfo;

import java.util.HashMap;
import java.util.List;
import java.util.Map;

public class NoInfrastructureRepository implements InfrastructureRepository {

private final Map<String, Node> nodes = new HashMap<>();

public NoInfrastructureRepository(NetworkGraphic networkGraphic) {
networkGraphic.getNodes().forEach(node -> nodes.put(node.getBetriebspunktName(), node));
}

@Override
public StopFacilityInfo getStopFacility(String stopId) {
Node node = nodes.get(stopId);
return new StopFacilityInfo(stopId, new Coordinate(-node.getPositionY(), node.getPositionX()));
}

@Override
public List<TrackSegmentInfo> getTrack(StopFacilityInfo fromStop, StopFacilityInfo toStop, TransitLineInfo transitLineInfo) {
Node fromNode = nodes.get(fromStop.getId());
Node toNode = nodes.get(toStop.getId());

Coordinate fromCoord = new Coordinate(fromNode.getPositionX(), fromNode.getPositionY());
Coordinate toCoord = new Coordinate(toNode.getPositionX(), toNode.getPositionY());
double distance = euclideanDistance(fromCoord, toCoord);

return List.of(
new TrackSegmentInfo(String.format("%s-%s", fromStop.getId(), toStop.getId()), fromCoord, toCoord,
distance));
}

private double euclideanDistance(Coordinate from, Coordinate to) {
// coordinates represent screen positions (not geographical); Euclidean formula is valid in this case
double deltaX = to.getLongitude() - from.getLongitude();
double deltaY = to.getLatitude() - from.getLatitude();

return Math.sqrt(deltaX * deltaX + deltaY * deltaY);
}
}
Loading