Skip to content

Commit

Permalink
Merge pull request #1 from munterfi/feature/default-converter
Browse files Browse the repository at this point in the history
Feature/default converter
  • Loading branch information
munterfi authored Nov 5, 2024
2 parents a5e6c71 + b2e6f84 commit 31c8c3e
Show file tree
Hide file tree
Showing 17 changed files with 421 additions and 41 deletions.
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

0 comments on commit 31c8c3e

Please sign in to comment.