Show when the vehicles were last seen in TTSS
| | |
| | | "require": { |
| | | "google/gtfs-realtime-bindings": "^0.0.2", |
| | | "monolog/monolog": "^1.24", |
| | | "twig/twig": "^2.0" |
| | | "twig/twig": "^2.0", |
| | | "twig/extensions": "^1.5" |
| | | } |
| | | } |
| | |
| | | <?php |
| | | $tramTypes = new TramTypes(); |
| | | $sources = [ |
| | | 'bus' => [ |
| | | 'gtfsrt' => 'ftp://zdmk.krakow.pl/VehiclePositions_A.pb', |
| | |
| | | 'database' => 'mapping_A.sqlite3', |
| | | 'result' => 'mapping_A.json', |
| | | 'result_vehicles' => 'vehicles_A.html', |
| | | 'mapper' => new BusTypes(), |
| | | 'vehicle_types' => new BusTypes(), |
| | | 'prefix' => 'b', |
| | | ], |
| | | 'tram' => [ |
| | | 'gtfsrt' => 'ftp://zdmk.krakow.pl/VehiclePositions_T.pb', |
| | | 'gtfsrt' => 'http://gtfs.ztp.krakow.pl/VehiclePositions_T.pb', |
| | | 'gtfsrt_file' => 'VehiclePositions_T.pb', |
| | | 'ttss' => 'http://www.ttss.krakow.pl/internetservice/geoserviceDispatcher/services/vehicleinfo/vehicles?positionType=CORRECTED', |
| | | 'ttss_file' => 'vehicles_T.json', |
| | | 'database' => 'mapping_T.sqlite3', |
| | | 'result' => 'mapping_T.json', |
| | | 'result_vehicles' => 'vehicles_T.html', |
| | | 'mapper' => $tramTypes, |
| | | 'vehicle_types' => new TramTypes(), |
| | | 'prefix' => 't', |
| | | ], |
| | | ]; |
| | |
| | | $this->pdo = new PDO('sqlite:'.$file); |
| | | $this->pdo->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION); |
| | | |
| | | $this->pdo->query('CREATE TABLE IF NOT EXISTS vehicles ( |
| | | $this->pdo->query('CREATE TABLE IF NOT EXISTS vehicles2 ( |
| | | id INT PRIMARY KEY, |
| | | num INT UNIQUE, |
| | | weight INT |
| | | weight INT, |
| | | line VARCHAR, |
| | | date INT |
| | | )'); |
| | | try { |
| | | $this->beginTransaction(); |
| | | $this->pdo->query('INSERT INTO vehicles2 SELECT id, num, weight, \'?\', \''.time().'\' FROM vehicles'); |
| | | $this->commit(); |
| | | $this->pdo->query('DROP TABLE vehicles'); |
| | | } catch(PDOException $e) { |
| | | $this->rollback(); |
| | | } |
| | | |
| | | $this->addStatement = $this->pdo->prepare('INSERT OR REPLACE INTO vehicles (id, num, weight) VALUES (:id, :num, :weight)'); |
| | | $this->addStatement = $this->pdo->prepare('INSERT OR REPLACE INTO vehicles2 (id, num, weight, line, date) VALUES (:id, :num, :weight, :line, :date)'); |
| | | |
| | | $this->_cacheClear(); |
| | | } |
| | |
| | | |
| | | protected function _cachePopulate() { |
| | | if($this->cacheId === NULL) { |
| | | $st = $this->pdo->prepare('SELECT * FROM vehicles'); |
| | | $st = $this->pdo->prepare('SELECT * FROM vehicles2'); |
| | | $st->execute(); |
| | | $result = $st->fetchAll(PDO::FETCH_ASSOC); |
| | | |
| | |
| | | $this->cacheNum = NULL; |
| | | } |
| | | |
| | | public function getAll() { |
| | | public function getAllById() { |
| | | $this->_cachePopulate(); |
| | | return $this->cacheId; |
| | | } |
| | | |
| | | public function getAllByNum() { |
| | | $this->_cachePopulate(); |
| | | return $this->cacheNum; |
| | | } |
| | | |
| | | public function getById($id) { |
| | |
| | | } |
| | | |
| | | public function clear() { |
| | | $this->pdo->query('DELETE FROM vehicles'); |
| | | $this->pdo->query('DELETE FROM vehicles2'); |
| | | $this->_cacheClear(); |
| | | } |
| | | |
| | | public function add($id, $num, $weight) { |
| | | public function add($id, $num, $weight, $line = NULL, $date = NULL) { |
| | | $vehicle = [ |
| | | 'id' => (string)$id, |
| | | 'num' => (string)$num, |
| | | 'weight' => (string)$weight |
| | | 'weight' => (string)$weight, |
| | | 'line' => (string)($line ?? ''), |
| | | 'date' => (string)($date ?? time()), |
| | | ]; |
| | | $this->addStatement->execute($vehicle); |
| | | $this->_cacheAdd($vehicle); |
| | | } |
| | | |
| | | public function addMapping($mapping) { |
| | | public function addMapping($vehiclesMapping, Mapper $mapper) { |
| | | $this->beginTransaction(); |
| | | $weight = count($mapping); |
| | | foreach($mapping as $id => $num) { |
| | | $this->add($id, $num, $weight); |
| | | $weight = count($vehiclesMapping); |
| | | foreach($vehiclesMapping as $id => $num) { |
| | | $trip = $mapper->getTTSSVehicleTrip($id); |
| | | $this->add($id, $num, $weight, ($trip['line'] ?? '?') . ' ' . ($trip['direction'] ?? '?')); |
| | | } |
| | | $this->commit(); |
| | | } |
| | |
| | | class Mapper { |
| | | private $ttssDate = NULL; |
| | | private $ttssTrips = []; |
| | | private $ttssVehicleToTrip = []; |
| | | private $gtfsrtDate = NULL; |
| | | private $gtfsrtTrips = []; |
| | | private $logger = NULL; |
| | |
| | | if(!isset($vehicle->latitude) || !$vehicle->latitude) continue; |
| | | if(!isset($vehicle->longitude) || !$vehicle->longitude) continue; |
| | | list($line, $direction) = explode(' ', $vehicle->name, 2); |
| | | foreach($this->specialNames as $specialName) { |
| | | if(substr($vehicle->name, -strlen($specialName)) == $specialName) { |
| | | continue; |
| | | } |
| | | } |
| | | $this->ttssTrips[(string)$vehicle->tripId] = [ |
| | | $trip = [ |
| | | 'id' => (string)$vehicle->id, |
| | | 'line' => $line, |
| | | 'direction' => $direction, |
| | | 'latitude' => (float)$vehicle->latitude / 3600000.0, |
| | | 'longitude' => (float)$vehicle->longitude / 3600000.0, |
| | | ]; |
| | | $this->ttssTrips[(string)$vehicle->tripId] = $trip; |
| | | $this->ttssVehicleToTrip[(string)$vehicle->id] = $trip; |
| | | } |
| | | ksort($this->ttssTrips); |
| | | } |
| | |
| | | |
| | | public function getTTSSTrips() { |
| | | return $this->ttssTrips; |
| | | } |
| | | |
| | | public function getTTSSVehicleToTrip() { |
| | | return $this->ttssVehicleToTrip; |
| | | } |
| | | |
| | | public function getTTSSTrip($id) { |
| | | return $this->ttssTrips[$id] ?? NULL; |
| | | } |
| | | |
| | | public function getTTSSVehicleTrip($id) { |
| | | return $this->ttssVehicleToTrip[$id] ?? NULL; |
| | | } |
| | | |
| | | public function loadGTFSRT($file) { |
| | |
| | | return $bestOffset; |
| | | } |
| | | |
| | | public function mapUsingOffset($offset) { |
| | | public function mapVehicleIdsUsingOffset($offset) { |
| | | $result = []; |
| | | foreach($this->gtfsrtTrips as $gtfsTripId => $gtfsTrip) { |
| | | $ttssTripId = $gtfsTripId + $offset; |
| | |
| | | <?php |
| | | class Output { |
| | | static function createMapping($db, VehicleTypes $vehicleTypes, $saveConfig = FALSE) { |
| | | private $db; |
| | | private $mapper; |
| | | private $vehicleTypes; |
| | | |
| | | function __construct(Database $db, Mapper $mapper, VehicleTypes $vehicleTypes) { |
| | | $this->db = $db; |
| | | $this->mapper = $mapper; |
| | | $this->vehicleTypes = $vehicleTypes; |
| | | } |
| | | |
| | | function createMapping($saveConfig = FALSE) { |
| | | $mapping = []; |
| | | foreach($db->getAll() as $vehicle) { |
| | | $mapping[$vehicle['id']] = $vehicleTypes->getByNumber($vehicle['num']); |
| | | foreach($this->db->getAllById() as $vehicle) { |
| | | $mapping[$vehicle['id']] = $this->vehicleTypes->getByNumber($vehicle['num']); |
| | | } |
| | | |
| | | if($saveConfig) { |
| | |
| | | return $mapping; |
| | | } |
| | | |
| | | function createVehiclesList($trips, $mapping, $saveConfig = FALSE) { |
| | | function createVehiclesList($fullMapping, $saveConfig = FALSE) { |
| | | $trips = $this->mapper->getTTSSTrips(); |
| | | |
| | | $lines = []; |
| | | $vehicles = []; |
| | | foreach($trips as $trip) { |
| | | $vehicle = $mapping[$trip['id']] ?? []; |
| | | $vehicle = $fullMapping[$trip['id']] ?? []; |
| | | $vehicle += ['trip' => $trip['id']]; |
| | | $lines[$trip['line']][] = [ |
| | | 'trip' => $trip, |
| | |
| | | ]; |
| | | $vehicles[$vehicle['type'] ?? '?'][] = $vehicle; |
| | | } |
| | | |
| | | foreach($lines as &$line) { |
| | | usort($line, function($a, $b) { |
| | | return (substr($a['vehicle']['num'] ?? '', 2) <=> substr($b['vehicle']['num'] ?? '', 2)); |
| | |
| | | } |
| | | unset($line); |
| | | ksort($lines); |
| | | |
| | | foreach($vehicles as &$vehicle) { |
| | | usort($vehicle, function($a, $b) { |
| | | return (substr($a['num'] ?? '', 2) <=> substr($b['num'] ?? '', 2)); |
| | |
| | | unset($vehicle); |
| | | ksort($vehicles); |
| | | |
| | | $dbMapping = $this->db->getAllByNum(); |
| | | ksort($dbMapping); |
| | | |
| | | if($saveConfig) { |
| | | $twigLoader = new \Twig\Loader\FilesystemLoader(__DIR__.'/../templates'); |
| | | $twig = new \Twig\Environment($twigLoader); |
| | | $twig->addExtension(new Twig_Extensions_Extension_Date()); |
| | | |
| | | $vehiclesHtml = $twig->render('vehicles.html', [ |
| | | 'lines' => $lines, |
| | | 'vehicles' => $vehicles, |
| | | 'prefix' => $saveConfig['prefix'], |
| | | 'mapping' => $dbMapping, |
| | | ]); |
| | | if(!file_put_contents($saveConfig['result_vehicles_temp'], $vehiclesHtml)) { |
| | | throw new Exception('Vehicles save failed'); |
| | |
| | | } |
| | | |
| | | $logger->info('Got offset '.$offset.', creating mapping...'); |
| | | $mapping = $mapper->mapUsingOffset($offset); |
| | | $mapping = $mapper->mapVehicleIdsUsingOffset($offset); |
| | | |
| | | $logger->info('Checking the data for correctness...'); |
| | | $weight = count($mapping); |
| | |
| | | throw new Exception('Ignoring result due to better data already present'); |
| | | } |
| | | |
| | | $output = new Output($db, $mapper, $source['vehicle_types']); |
| | | |
| | | $logger->info('Creating mapping...'); |
| | | $logger->info('Saving mapping...'); |
| | | |
| | | $db->addMapping($mapping); |
| | | $db->addMapping($mapping, $mapper); |
| | | |
| | | $finalMapping = Output::createMapping($db, $source['mapper'], $source); |
| | | |
| | | $fullMapping = $output->createMapping($source); |
| | | |
| | | $logger->info('Creating vehicle list...'); |
| | | |
| | | Output::createVehiclesList($mapper->getTTSSTrips(), $finalMapping, $source); |
| | | $output->createVehiclesList($fullMapping, $source); |
| | | |
| | | $logger->info('Finished'); |
| | | } catch(Throwable $e) { |
| | |
| | | try { |
| | | $logger->info('Regenerating '.$name.'...'); |
| | | $db = new Database($source['database']); |
| | | Output::createMapping($db, $source['mapper'], $source); |
| | | $mapper = new Mapper(); |
| | | $mapper->loadTTSS($source['ttss_file']); |
| | | $output = new Output($db, $mapper, $source['vehicle_types']); |
| | | $fullMapping = $output->createMapping($source); |
| | | $output->createVehiclesList($fullMapping, $source); |
| | | $logger->info('Finished'); |
| | | } catch(Throwable $e) { |
| | | $logger->error($e->getMessage(), ['exception' => $e, 'exception_string' => (string)$e]); |
| | |
| | | {% endfor %} |
| | | </table> |
| | | |
| | | <table> |
| | | <caption>Vehicles in TTSS</caption> |
| | | <thead> |
| | | <tr> |
| | | <th>Vehicle</th> <th>Line</th> <th>Last seen</th> |
| | | </tr> |
| | | </thead> |
| | | {% for map in mapping %} |
| | | <tr> |
| | | <td> |
| | | {{ map.num | e }} |
| | | </td> |
| | | <td> |
| | | {{ map.line | default('?') | e }} |
| | | </td> |
| | | <td> |
| | | {{ map.date | time_diff | default('now') }} |
| | | </td> |
| | | </tr> |
| | | {% endfor %} |
| | | </table> |
| | | |
| | | Generated at {{ "now" | date("Y-m-d H:i:s P") }} |
| | | |
| | | </table> |