/**
  * @param PDO $db
  * @param string $station
  * @param string $scanDate
  */
 public function calculateTransferPatternsForStation(PDO $db, $station, $scanDate)
 {
     $db->beginTransaction();
     $insertPattern = $db->prepare("INSERT INTO transfer_pattern VALUES (null, ?, ?, ?, ?, ?)");
     $insertLegSQL = $db->prepare("INSERT INTO transfer_pattern_leg VALUES (null, ?, ?, ?)");
     $existingPatterns = $this->getExistingPatterns($db, $station);
     $treeBuilder = new ConnectionScanner($this->timetable, $this->nonTimetableConnections, $this->interchange);
     $tree = $treeBuilder->getShortestPathTree($station, 18000);
     /** @var Journey $pattern */
     foreach ($tree as $destination => $patterns) {
         foreach ($patterns as $pattern) {
             $hash = $pattern->getHash($station, $destination);
             $legs = $pattern->getTimetableLegs();
             if (isset($existingPatterns[$hash]) || count($legs) > 7 || count($legs) === 0) {
                 continue;
             }
             //                error_log("Found {$stoppingPattern}");
             $duration = $pattern->getDuration();
             $insertPattern->execute([$station, $destination, $duration, $scanDate . ' ' . gmdate("H:i", $pattern->getDepartureTime()), $scanDate]);
             $patternId = $db->lastInsertId();
             $existingPatterns[$hash] = $duration;
             foreach ($pattern->getTimetableLegs() as $leg) {
                 $insertLegSQL->execute([$patternId, $leg->getOrigin(), $leg->getDestination()]);
             }
         }
     }
     $db->commit();
 }
 /**
  * Try to use the connection that involves the least number of changes.
  *
  * @param Connection $connection
  * @return bool
  * @throws PlanningException
  */
 protected function thisConnectionIsBetter(Connection $connection)
 {
     $firstVisit = !isset($this->arrivals[$connection->getDestination()]);
     $requiresChange = !isset($this->connections[$connection->getOrigin()]) || $connection->requiresInterchangeWith($this->connections[$connection->getOrigin()]);
     $numChanges = $this->changes[$connection->getOrigin()] + intval($requiresChange);
     if ($firstVisit || $numChanges < $this->changes[$connection->getDestination()] || $numChanges === $this->changes[$connection->getDestination()] && parent::thisConnectionIsBetter($connection)) {
         $this->changes[$connection->getDestination()] = $numChanges;
         return true;
     }
     return false;
 }
 /**
  * @param  OutputInterface $out
  * @param  string          $origin
  * @param  string          $destination
  * @param  int             $targetTime
  */
 private function planJourney(OutputInterface $out, $origin, $destination, $targetTime)
 {
     $this->outputHeading($out, "Journey Planner");
     $timetableConnections = $this->outputTask($out, "Loading timetable", function () use($targetTime, $origin) {
         return $this->scheduleProvider->getTimetableConnections($targetTime);
     });
     $nonTimetableConnections = $this->outputTask($out, "Loading non timetable connections", function () use($targetTime) {
         return $this->scheduleProvider->getNonTimetableConnections($targetTime);
     });
     $interchangeTimes = $this->outputTask($out, "Loading interchange", function () {
         return $this->scheduleProvider->getInterchangeTimes();
     });
     $locations = $this->outputTask($out, "Loading locations", function () {
         return $this->stationProvider->getLocations();
     });
     $scanner = new ConnectionScanner($timetableConnections, $nonTimetableConnections, $interchangeTimes);
     $route = $this->outputTask($out, "Plan journey", function () use($scanner, $targetTime, $origin, $destination) {
         return $scanner->getJourneys($origin, $destination, strtotime('1970-01-01 ' . gmdate('H:i:s', $targetTime) . ' UTC'));
     });
     if (count($route) === 0) {
         $out->writeln("No route found.");
     } else {
         $this->displayRoute($out, $locations, $route[0]);
     }
     $this->outputMemoryUsage($out);
     $out->writeln("Connections: " . count($timetableConnections));
 }
 /**
  * This is modelled on MYB -> WWW at 20:00 on a weekday. It puts you on a
  * train to Haddenham when you could just wait an extra 3 mins at MYB.
  *
  * The connection from MYB to Haddenham actually has less calling points
  * but it doesn't matter as it still connects to the MYB service.
  */
 public function testUnnecessaryChangeWithDifferentCallingPoints()
 {
     $timetable = [new TimetableConnection("A", "B", 1000, 1010, "CS1000", "LN"), new TimetableConnection("B", "C", 1011, 1012, "CS1000", "LN"), new TimetableConnection("A", "C", 1005, 1015, "CS1001", "LN"), new TimetableConnection("C", "D", 1020, 1045, "CS1001", "LN")];
     $interchangeTimes = ["C" => 1];
     $expected = [new Journey([new Leg([new TimetableConnection("A", "C", 1005, 1015, "CS1001", "LN"), new TimetableConnection("C", "D", 1020, 1045, "CS1001", "LN")])])];
     $scanner = new ConnectionScanner($timetable, [], $interchangeTimes);
     $route = $scanner->getJourneys("A", "D", 900);
     $this->assertEquals($expected, $route);
 }