Skip to content

Commit

Permalink
FIX: NAV-90 - Add parent and children relation to stop, replace strin…
Browse files Browse the repository at this point in the history
…g id

- Add validation that parent ids also exist when parsing GTFS stops.
- Remove stop parent map in service, since already present in GTFS stops.
  • Loading branch information
munterfi committed Jun 4, 2024
1 parent 3405c5c commit 673577a
Show file tree
Hide file tree
Showing 3 changed files with 61 additions and 44 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -13,9 +13,7 @@

import java.time.DayOfWeek;
import java.time.LocalDate;
import java.util.EnumSet;
import java.util.HashMap;
import java.util.Map;
import java.util.*;

/**
* Implements a builder pattern for constructing instances of {@link GtfsSchedule}. This builder helps assemble a GTFS
Expand All @@ -38,6 +36,7 @@ public class GtfsScheduleBuilder {
private final Map<String, Stop> stops = new HashMap<>();
private final Map<String, Route> routes = new HashMap<>();
private final Map<String, Trip> trips = new HashMap<>();
private final Map<String, List<Stop>> parents = new HashMap<>();

private boolean built = false;

Expand All @@ -57,7 +56,9 @@ public GtfsScheduleBuilder addStop(String id, String name, @Nullable String pare
throw new IllegalArgumentException("Stop " + id + " already exists");
}
log.debug("Adding stop {}", id);
stops.put(id, new Stop(id, name, parentStop, new GeoCoordinate(lat, lon)));
Stop stop = new Stop(id, name, new GeoCoordinate(lat, lon));
parents.computeIfAbsent(parentStop, ignored -> new ArrayList<>()).add(stop);
stops.put(id, stop);
return this;
}

Expand Down Expand Up @@ -171,13 +172,28 @@ public GtfsScheduleBuilder addTransfer(String fromStopId, String toStopId, Trans
public GtfsSchedule build() {
checkNotBuilt();
log.info("Building schedule with {} stops, {} routes and {} trips", stops.size(), routes.size(), trips.size());

// set parents for child
parents.forEach((parentId, children) -> {
Stop parent = stops.get(parentId);
if (parent == null) {
log.warn("Parent {} does not exist", parentId);
} else {
parent.setChildren(children);
children.forEach(child -> child.setParent(parent));
}
});

// initialize: make immutable and resize arrays to capacity
trips.values().parallelStream().forEach(Initializable::initialize);
stops.values().parallelStream().forEach(Initializable::initialize);
routes.values().parallelStream().forEach(Initializable::initialize);
calendars.values().parallelStream().forEach(Initializable::initialize);

GtfsSchedule schedule = new GtfsSchedule(agencies, calendars, stops, routes, trips);
clear();
built = true;

return schedule;
}

Expand Down
19 changes: 13 additions & 6 deletions src/main/java/ch/naviqore/gtfs/schedule/model/Stop.java
Original file line number Diff line number Diff line change
Expand Up @@ -5,22 +5,24 @@
import lombok.AccessLevel;
import lombok.Getter;
import lombok.RequiredArgsConstructor;
import lombok.Setter;
import org.jetbrains.annotations.Nullable;

import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Objects;
import java.util.*;

@RequiredArgsConstructor(access = AccessLevel.PACKAGE)
@Getter
public final class Stop implements Initializable, Location<GeoCoordinate> {

private final String id;
private final String name;
@Nullable
private final String parentId;
private final GeoCoordinate coordinate;
@Nullable
@Setter(AccessLevel.PACKAGE)
@Getter(AccessLevel.NONE)
private Stop parent;
@Setter(AccessLevel.PACKAGE)
private List<Stop> children = new ArrayList<>();
private List<StopTime> stopTimes = new ArrayList<>();
private List<Transfer> transfers = new ArrayList<>();

Expand All @@ -32,9 +34,14 @@ void addTransfer(Transfer transfer) {
transfers.add(transfer);
}

public Optional<Stop> getParent() {
return Optional.ofNullable(parent);
}

@Override
public void initialize() {
Collections.sort(stopTimes);
children = List.copyOf(children);
stopTimes = List.copyOf(stopTimes);
transfers = List.copyOf(transfers);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,7 @@ public class PublicTransitServiceImpl implements PublicTransitService {
private static final int MIN_WALK_DURATION = 120;
private final ServiceConfig config;
private final GtfsSchedule schedule;
private final Map<String, List<ch.naviqore.gtfs.schedule.model.Stop>> parentStops;
// private final Map<String, List<ch.naviqore.gtfs.schedule.model.Stop>> parentStops;
private final KDTree<ch.naviqore.gtfs.schedule.model.Stop> spatialStopIndex;
private final SearchIndex<ch.naviqore.gtfs.schedule.model.Stop> stopSearchIndex;
private final WalkCalculator walkCalculator;
Expand All @@ -53,8 +53,8 @@ public PublicTransitServiceImpl(ServiceConfig config) {
this.config = config;
this.walkCalculator = Initializer.initializeWalkCalculator(config);
schedule = Initializer.readGtfsSchedule(config.getGtfsUrl());
parentStops = Initializer.groupStopsByParent(schedule);
stopSearchIndex = Initializer.generateStopSearchIndex(schedule, parentStops.keySet());
// parentStops = Initializer.groupStopsByParent(schedule);
stopSearchIndex = Initializer.generateStopSearchIndex(schedule);
spatialStopIndex = Initializer.generateSpatialStopIndex(schedule);

// todo: make transfer generators configurable through application properties
Expand Down Expand Up @@ -82,8 +82,9 @@ public List<Stop> getStops(String like, SearchType searchType) {
public Optional<Stop> getNearestStop(GeoCoordinate location) {
log.debug("Get nearest stop to {}", location);
ch.naviqore.gtfs.schedule.model.Stop stop = spatialStopIndex.nearestNeighbour(location);
if (stop != null && stop.getParentId() != null && !stop.getParentId().equals(stop.getId())) {
stop = schedule.getStops().get(stop.getParentId());
// if nearest stop, which could be null, is a child stop, return parent stop
if (stop != null && stop.getParent().isPresent() && !stop.getParent().get().equals(stop)) {
stop = stop.getParent().get();
}
return Optional.ofNullable(map(stop));
}
Expand All @@ -95,9 +96,11 @@ public List<Stop> getNearestStops(GeoCoordinate location, int radius, int limit)
List<ch.naviqore.gtfs.schedule.model.Stop> stops = new ArrayList<>();

for (ch.naviqore.gtfs.schedule.model.Stop stop : spatialStopIndex.rangeSearch(location, radius)) {
if (stop.getParentId() != null && !stop.getParentId().equals(stop.getId())) {
stop = schedule.getStops().get(stop.getParentId());
// if nearest stop is a child stop, return parent stop
if (stop.getParent().isPresent() && !stop.getParent().get().equals(stop)) {
stop = stop.getParent().get();
}
// avoid adding same parent stop multiple times
if (!stops.contains(stop)) {
stops.add(stop);
}
Expand All @@ -119,20 +122,21 @@ public List<StopTime> getNextDepartures(Stop stop, LocalDateTime from, @Nullable
.toList();
}

// returns all stops to be included in search
private List<String> getAllStopIdsForStop(Stop stop) {
List<String> stopIds;
if (parentStops.containsKey(stop.getId())) {
stopIds = getAllStopIdsForParentStop(stop.getId());
ch.naviqore.gtfs.schedule.model.Stop scheduleStop = schedule.getStops().get(stop.getId());

if (scheduleStop.getChildren().isEmpty()) {
// child stop; return itself
return List.of(stop.getId());
} else {
stopIds = List.of(stop.getId());
}
return stopIds;
}
// parent stop; return all children and itself (departures on parent are possible)
List<String> stopIds = new ArrayList<>();
stopIds.add(scheduleStop.getId());
scheduleStop.getChildren().forEach(child -> stopIds.add(child.getId()));

private List<String> getAllStopIdsForParentStop(String stopId) {
ch.naviqore.gtfs.schedule.model.Stop gtfsStop = schedule.getStops().get(stopId);
String parentId = gtfsStop.getParentId() == null ? gtfsStop.getId() : gtfsStop.getParentId();
return parentStops.get(parentId).stream().map(ch.naviqore.gtfs.schedule.model.Stop::getId).toList();
return stopIds;
}
}

@Override
Expand Down Expand Up @@ -378,25 +382,15 @@ private static GtfsSchedule readGtfsSchedule(String gtfsFilePath) {
}
}

private static Map<String, List<ch.naviqore.gtfs.schedule.model.Stop>> groupStopsByParent(
private static SearchIndex<ch.naviqore.gtfs.schedule.model.Stop> generateStopSearchIndex(
GtfsSchedule schedule) {
Map<String, List<ch.naviqore.gtfs.schedule.model.Stop>> parentStopIds = new HashMap<>();
SearchIndexBuilder<ch.naviqore.gtfs.schedule.model.Stop> builder = SearchIndex.builder();

// only add parent stops and stops without a parent
for (ch.naviqore.gtfs.schedule.model.Stop stop : schedule.getStops().values()) {
String id = stop.getParentId() == null ? stop.getId() : stop.getParentId();
if (!parentStopIds.containsKey(id)) {
parentStopIds.put(id, new ArrayList<>());
if (stop.getParent().isEmpty()) {
builder.add(stop.getName().toLowerCase(), stop);
}
parentStopIds.get(id).add(stop);
}
return parentStopIds;
}

private static SearchIndex<ch.naviqore.gtfs.schedule.model.Stop> generateStopSearchIndex(GtfsSchedule schedule,
Set<String> parentStops) {
SearchIndexBuilder<ch.naviqore.gtfs.schedule.model.Stop> builder = SearchIndex.builder();
for (String parentStopId : parentStops) {
ch.naviqore.gtfs.schedule.model.Stop parentStop = schedule.getStops().get(parentStopId);
builder.add(parentStop.getName().toLowerCase(), parentStop);
}

return builder.build();
Expand Down

0 comments on commit 673577a

Please sign in to comment.