Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
16 changes: 5 additions & 11 deletions src/main/java/pl/agh/transit/DirectConnectionService.java
Original file line number Diff line number Diff line change
Expand Up @@ -8,29 +8,23 @@
import pl.agh.transit.dto.TripUpdateDTO;
import pl.agh.transit.gtfs_static.model.Stop;
import pl.agh.transit.gtfs_static.model.StopTime;
import pl.agh.transit.gtfs_static.repository.RouteRepository;
import pl.agh.transit.gtfs_static.repository.StaticTripRepository;
import pl.agh.transit.gtfs_static.repository.StopRepository;
import pl.agh.transit.gtfs_static.repository.StopTimeRepository;

import java.time.LocalDate;
import java.time.LocalDateTime;
import java.time.LocalTime;
import java.util.Comparator;
import java.util.List;
import java.util.Objects;
import java.util.Optional;
import java.util.stream.Stream;

@Slf4j
@Service
@AllArgsConstructor
public class DirectConnectionService {
private final StopRepository stopRepository;
private final StopTimeRepository stopTimeRepository;
private final RouteRepository routeRepository;
private final StaticTripRepository staticTripRepository;
private final TripRepository tripRepository;
private final CalendarService calendarService;
private final TripMapper tripMapper;
private final ConnectionServiceHelper connectionServiceHelper;

Expand All @@ -40,16 +34,16 @@ public class DirectConnectionService {
* Returns up to 'count' number of connections, sorted by departure time
* Uses today's date and current time by default
*/
public List<TransportTypeDTO> findFastestDirectConnection(String fromStopName, String toStopName) {
return findFastestDirectConnection(fromStopName, toStopName, LocalDate.now(), LocalTime.now(), 1);
public List<TransportTypeDTO> findFastestDirectConnections(String fromStopName, String toStopName, int count) {
return findFastestDirectConnections(fromStopName, toStopName, LocalDate.now(), LocalTime.now(), count);
}

/**
* Finds multiple fastest direct connections from one stop to another for a specific date and time
* Returns up to 'count' number of connections, sorted by departure time
* Takes into account delays from GTFS Realtime and service calendar
*/
public List<TransportTypeDTO> findFastestDirectConnection(String fromStopName, String toStopName, LocalDate travelDate, LocalTime travelTime, int count) {
public List<TransportTypeDTO> findFastestDirectConnections(String fromStopName, String toStopName, LocalDate travelDate, LocalTime travelTime, int count) {
log.debug("[1] findFastestDirectConnections START: {} -> {} on {} at {} (count: {})", fromStopName, toStopName, travelDate, travelTime, count);

List<Stop> fromStops = connectionServiceHelper.getStopsForName(fromStopName);
Expand Down Expand Up @@ -200,7 +194,7 @@ private List<TransportTypeDTO> findStaticConnection(String fromStopId, List<Stop
})
.flatMap(fromStopTime -> findStaticDestinationStop(fromStopTime, toStopCandidates, fromStopId))
.map(toStopTime -> buildStaticTransportDTO(toStopTime, fromStopId, travelDate))
.filter(transport -> transport != null)
.filter(Objects::nonNull)
.sorted(Comparator.comparing(TransportTypeDTO::getDepartureTime))
.toList();
}
Expand Down
3 changes: 1 addition & 2 deletions src/main/java/pl/agh/transit/StopController.java
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,6 @@
import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
import pl.agh.transit.dto.PagedResponseDTO;
Expand Down Expand Up @@ -42,10 +41,10 @@ public ResponseEntity<List<NextDepartureDTO>> getNextDepartures(
@RequestParam String routeName,
@RequestParam(required = false) @DateTimeFormat(iso = DateTimeFormat.ISO.DATE) LocalDate date
) {
//TODO: consider adding time parameter in the future and handling it in the service layer
LocalDate travelDate = date != null ? date : LocalDate.now();
List<NextDepartureDTO> departures = stopService.getNextDeparturesForStopAndRoute(
stopName, routeName, travelDate, 5);
return ResponseEntity.ok(departures);
}

}
20 changes: 10 additions & 10 deletions src/main/java/pl/agh/transit/TripController.java
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,6 @@
import java.time.LocalDate;
import java.time.LocalTime;
import java.util.List;
import java.util.Optional;

@RestController
@AllArgsConstructor
Expand All @@ -26,7 +25,7 @@ public List<TripUpdateDTO> getTrips() {
return tripService.getTripUpdates();
}

@GetMapping(value = "/trips/random-stop-time", produces = MediaType.APPLICATION_JSON_VALUE)
@GetMapping(value = "/random-stop-time", produces = MediaType.APPLICATION_JSON_VALUE)
public StopTimeUpdateDTO getRandomStopTime() {
return tripService.getRandomStopTime();
}
Expand All @@ -36,17 +35,18 @@ public TransportTypeDTO getDirectConnection(@RequestParam String from, @RequestP
return tripService.getDirectConnection(from, to);
}


@GetMapping(value = "/fastest-direct-connection/with-date", produces = MediaType.APPLICATION_JSON_VALUE)
public List<TransportTypeDTO> getDirectConnectionsWithDate(
@GetMapping(value = "/fastest-direct-connections", produces = MediaType.APPLICATION_JSON_VALUE)
public List<TransportTypeDTO> getFastestConnections(
@RequestParam String from,
@RequestParam String to,
@RequestParam String travelDate,
@RequestParam String travelTime,
@RequestParam(required = false) LocalDate travelDate, // (YYYY-MM-DD)
@RequestParam(required = false) LocalTime travelTime, // (HH:mm)
@RequestParam(defaultValue = "3") int count
) {
LocalDate date = LocalDate.parse(travelDate);
LocalTime time = LocalTime.parse(travelTime);
return directConnectionService.findFastestDirectConnection(from, to, date, time, count);
if (travelDate != null && travelTime != null) {
return directConnectionService.findFastestDirectConnections(from, to, travelDate, travelTime, count);
} else {
return directConnectionService.findFastestDirectConnections(from, to, count);
}
}
}
17 changes: 8 additions & 9 deletions src/main/java/pl/agh/transit/TripService.java
Original file line number Diff line number Diff line change
Expand Up @@ -39,20 +39,19 @@ public StopTimeUpdateDTO getRandomStopTime(){
.orElseThrow(() -> new ServiceUnavailableException("No stop time updates available"));
}

private <T> Optional<T> pickRandom(List<T> list){
if (list.isEmpty()){
return Optional.empty();
}
int randomIndex = random.nextInt(list.size());
return Optional.of(list.get(randomIndex));
}

public TransportTypeDTO getDirectConnection(String from , String to){
List<TransportTypeDTO> connection = directConnectionService.findFastestDirectConnection(from, to);
List<TransportTypeDTO> connection = directConnectionService.findFastestDirectConnections(from, to, 1);
if (connection.isEmpty()) {
throw new ServiceUnavailableException("No connection available from " + from + " to " + to);
}
return connection.getFirst();
}

private <T> Optional<T> pickRandom(List<T> list){
if (list.isEmpty()){
return Optional.empty();
}
int randomIndex = random.nextInt(list.size());
return Optional.of(list.get(randomIndex));
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -24,8 +24,6 @@
import pl.agh.transit.gtfs_static.repository.StopRepository;
import pl.agh.transit.gtfs_static.repository.StopTimeRepository;

import java.time.LocalDate;

@SpringBootTest
@Transactional
@DisplayName("DirectConnectionService Integration Tests")
Expand Down Expand Up @@ -154,88 +152,88 @@ private void setupTestData() {
@DisplayName("Should find fastest direct connection from static data")
void findFastestDirectConnection_staticData_returnsFastestRoute() {
List<TransportTypeDTO> result = directConnectionService
.findFastestDirectConnection("Main Station", "City Center", LocalDate.now(), LocalTime.now(), 1);
.findFastestDirectConnections("Main Station", "City Center", LocalDate.now(), LocalTime.now(), 1);

assertThat(result).hasSize(1);
assertThat(result.get(0).getRouteName()).isEqualTo("1A");
}

@Test
@DisplayName("Should return empty when no direct connection exists")
void findFastestDirectConnection_noDirectConnection_returnsEmpty() {
void findFastestDirectConnection_noDirectConnections_returnsEmpty() {
List<TransportTypeDTO> result = directConnectionService
.findFastestDirectConnection("Main Station", "Final Stop", LocalDate.now(), LocalTime.now(), 1);
.findFastestDirectConnections("Main Station", "Final Stop", LocalDate.now(), LocalTime.now(), 1);

assertThat(result).isEmpty();
}

@Test
@DisplayName("Should return empty when source stop does not exist")
void findFastestDirectConnection_sourceStopNotFound_returnsEmpty() {
void findFastestDirectConnections_sourceStopNotFound_returnsEmpty() {
List<TransportTypeDTO> result = directConnectionService
.findFastestDirectConnection("Unknown Station", "City Center", LocalDate.now(), LocalTime.now(), 1);
.findFastestDirectConnections("Unknown Station", "City Center", LocalDate.now(), LocalTime.now(), 1);

assertThat(result).isEmpty();
}

@Test
@DisplayName("Should return empty when destination stop does not exist")
void findFastestDirectConnection_destinationStopNotFound_returnsEmpty() {
void findFastestDirectConnections_destinationStopNotFound_returnsEmpty() {
List<TransportTypeDTO> result = directConnectionService
.findFastestDirectConnection("Main Station", "Unknown Station", LocalDate.now(), LocalTime.now(), 1);
.findFastestDirectConnections("Main Station", "Unknown Station", LocalDate.now(), LocalTime.now(), 1);

assertThat(result).isEmpty();
}

@Test
@DisplayName("Should return empty when both stops are the same")
void findFastestDirectConnection_sameSourceAndDestination_returnsEmpty() {
void findFastestDirectConnections_sameSourceAndDestination_returnsEmpty() {
List<TransportTypeDTO> result = directConnectionService
.findFastestDirectConnection("Main Station", "Main Station", LocalDate.now(), LocalTime.now(), 1);
.findFastestDirectConnections("Main Station", "Main Station", LocalDate.now(), LocalTime.now(), 1);

assertThat(result).isEmpty();
}

@Test
@DisplayName("Should return empty when travel date is outside calendar range")
void findFastestDirectConnection_travelDateOutsideCalendar_returnsEmpty() {
void findFastestDirectConnections_travelDateOutsideCalendar_returnsEmpty() {
LocalDate futureDate = LocalDate.now().plusYears(1);

List<TransportTypeDTO> result = directConnectionService
.findFastestDirectConnection("Main Station", "City Center", futureDate, LocalTime.now(), 1);
.findFastestDirectConnections("Main Station", "City Center", futureDate, LocalTime.now(), 1);

assertThat(result).isEmpty();
}

@Test
@DisplayName("Should find connection when travel date is within calendar range")
void findFastestDirectConnection_validTravelDate_returnsConnection() {
void findFastestDirectConnection_validTravelDate_returnsConnections() {
LocalDate validDate = LocalDate.now();

List<TransportTypeDTO> result = directConnectionService
.findFastestDirectConnection("Main Station", "City Center", validDate, LocalTime.now(), 1);
.findFastestDirectConnections("Main Station", "City Center", validDate, LocalTime.now(), 1);

assertThat(result).hasSize(1);
assertThat(result.get(0).getRouteName()).isEqualTo("1A");
}

@Test
@DisplayName("Should compare departure times and return earliest departure route")
void findFastestDirectConnection_comparesDepartureTimes_returnsEarliestDepartureRoute() {
void findFastestDirectConnections_comparesDepartureTimes_returnsEarliestDepartureRoute() {
// Trip 1: Departure 23:01:00, Arrival 23:20:00
// Trip 2: Departure 23:06:00, Arrival 23:10:00
// Should select trip 1 because it departs earliest

List<TransportTypeDTO> result = directConnectionService
.findFastestDirectConnection("Main Station", "City Center", LocalDate.now(), LocalTime.now(), 1);
.findFastestDirectConnections("Main Station", "City Center", LocalDate.now(), LocalTime.now(), 1);

assertThat(result).hasSize(1);
assertThat(result.get(0).getRouteName()).isEqualTo("1A");
}

@Test
@DisplayName("Should ignore trips not active on given date")
void findFastestDirectConnection_inactiveServicesIgnored_returnsActiveRoute() {
void findFastestDirectConnections_inactiveServicesIgnored_returnsActiveRoute() {
// Create inactive calendar for today
Calendar inactiveCalendar = new Calendar();
inactiveCalendar.setId("service-inactive");
Expand Down Expand Up @@ -280,7 +278,7 @@ void findFastestDirectConnection_inactiveServicesIgnored_returnsActiveRoute() {
stopTimeRepository.save(stopTime3To);

List<TransportTypeDTO> result = directConnectionService
.findFastestDirectConnection("Main Station", "City Center", LocalDate.now(), LocalTime.now(), 1);
.findFastestDirectConnections("Main Station", "City Center", LocalDate.now(), LocalTime.now(), 1);

// Should select route 1A (active, earliest departure), not 3C (inactive)
assertThat(result).hasSize(1);
Expand All @@ -289,7 +287,7 @@ void findFastestDirectConnection_inactiveServicesIgnored_returnsActiveRoute() {

@Test
@DisplayName("Should handle multiple stops with same name")
void findFastestDirectConnection_multipleStopsSameName_handlesCorrectly() {
void findFastestDirectConnections_multipleStopsSameName_handlesCorrectly() {
// Create second stop with same name
Stop duplicateStop = new Stop();
duplicateStop.setId("stop-main-2");
Expand All @@ -299,7 +297,7 @@ void findFastestDirectConnection_multipleStopsSameName_handlesCorrectly() {
stopRepository.save(duplicateStop);

List<TransportTypeDTO> result = directConnectionService
.findFastestDirectConnection("Main Station", "City Center", LocalDate.now(), LocalTime.now(), 1);
.findFastestDirectConnections("Main Station", "City Center", LocalDate.now(), LocalTime.now(), 1);

// Should find connection even with multiple stops with same name
assertThat(result).hasSize(1);
Expand All @@ -308,9 +306,9 @@ void findFastestDirectConnection_multipleStopsSameName_handlesCorrectly() {

@Test
@DisplayName("Should return valid arrival time for connection")
void findFastestDirectConnection_returnsNonNullArrivalTime() {
void findFastestDirectConnections_returnsNonNullArrivalTime() {
List<TransportTypeDTO> result = directConnectionService
.findFastestDirectConnection("Main Station", "City Center", LocalDate.now(), LocalTime.now(), 1);
.findFastestDirectConnections("Main Station", "City Center", LocalDate.now(), LocalTime.now(), 1);

assertThat(result).hasSize(1);
assertThat(result.get(0).getArrivalTime()).isNotNull();
Expand Down
Loading