Improved www.ttss.krakow.pl
Jacek Kowalski
2017-03-17 ced30990541a35ded43912901ba67c9a4b30ed5a
Use local database for stop name autocompletion
1 files modified
6 files added
356 ■■■■■ changed files
index.js 6 ●●●● patch | view | raw | blame | history
stops.php 70 ●●●●● patch | view | raw | blame | history
stops/common.php 17 ●●●●● patch | view | raw | blame | history
stops/download_stops.php 33 ●●●●● patch | view | raw | blame | history
stops/populate_db.php 27 ●●●●● patch | view | raw | blame | history
stops/stops.db patch | view | raw | blame | history
stops/stops.php 203 ●●●●● patch | view | raw | blame | history
index.js
@@ -500,11 +500,11 @@
    if(stop_name_autocomplete_xhr) stop_name_autocomplete_xhr.abort();
    
    stop_name_autocomplete_xhr = $.get(
        ttss_base + '/lookup/autocomplete/json'
            + '?query=' + encodeURIComponent(stop_name.value)
        'stops.php?query=' + encodeURIComponent(stop_name.value)
    ).done(function(data) {
        deleteChildren(stop_name_autocomplete);
        for(var i = 1, il = data.length; i < il; i++) {
        for(var i = 0, il = data.length; i < il; i++) {
            if(data[i].type != 'stop') continue;
            if(data[i].id > 6000) continue;
            var opt = document.createElement('option');
            opt.value = data[i].id;
stops.php
New file
@@ -0,0 +1,70 @@
<?php
include(__DIR__.'/stops/common.php');
include(__DIR__.'/stops/stops.php');
try {
    // Reject invalid input
    if(!isset($_GET['query'])) throw new UnexpectedValueException();
    if(empty($_GET['query'])) throw new UnexpectedValueException();
    if(strlen($_GET['query']) > 50) throw new UnexpectedValueException();
    // Initialize DB connection an query
    $pdo = new PDO('sqlite:stops/stops.db', NULL, NULL, array(
        PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION
    ));
    $st = $pdo->prepare('SELECT DISTINCT id FROM stop_search WHERE word LIKE ?');
    // Split stop name into words
    $words = split_stop_name($_GET['query']);
    // Find relevant stop IDs
    $ids = NULL;
    foreach($words as $word) {
        if(empty($word)) continue;
        // Find stop IDs with names matching the word
        $st->execute(array($word.'%'));
        $results = $st->fetchAll(PDO::FETCH_COLUMN);
        $st->closeCursor();
        // Merge results with previous searches
        if(is_array($ids)) {
            $ids = array_intersect($ids, $results);
        } else {
            $ids = $results;
        }
        // No results will be found
        if(count($ids) == 0) break;
    }
    // Close DB connection
    unset($st, $pdo);
    // No query was executed
    if(!is_array($ids)) throw new UnexpectedValueException();
    // Build structure for UI
    $stop_list = [];
    foreach($ids as $id) {
        $stop_list[] = [
            'id' => $id,
            'name' => $stops[$id],
            'type' => 'stop',
            'relevance' => similar_text($_GET['query'], $stops[$id])
        ];
    }
    // Sort stops by relevence
    usort($stop_list, function($a, $b) {
        return $b['relevance'] - $a['relevance'];
    });
    // Return JSON
    echo json_encode($stop_list);
} catch(UnexpectedValueException $e) {
    echo '[]';
} catch(Exception $e) {
    header('HTTP/1.1 503 Service Unavailable');
    echo $e->getMessage();
}
stops/common.php
New file
@@ -0,0 +1,17 @@
<?php
setlocale(LC_CTYPE, 'pl_PL.UTF-8');
function split_stop_name($string) {
    $string = strtolower(iconv('utf-8', 'ascii//TRANSLIT', $string));
    $words = preg_split('/\\W+/', $string);
    foreach($words as &$word) {
        $two = substr($word, 0, 2);
        if($two == 'os') $word = 'os';
        elseif($two == 'al') $word = 'al';
        elseif($two == 'sw') $word = 'sw';
    }
    unset($word);
    return $words;
}
stops/download_stops.php
New file
@@ -0,0 +1,33 @@
<?php
if(php_sapi_name() !== 'cli') die();
$chars = 'aąbcćdeęfghijklłmnńoóprsśtuvwxyzżź0123456789';
$len = mb_strlen($chars);
$replacements = [
    '&Oacute;' => 'Ó',
    '&oacute;' => 'ó',
    '&Eacute;' => 'É',
    '&eacute;' => 'é',
];
$stops = [];
for($i = 0; $i < $len; $i++) {
    for($j = 0; $j < $len; $j++) {
        $char = mb_substr($chars, $i, 1).mb_substr($chars, $j, 1);
        $json = file_get_contents('http://www.ttss.krakow.pl/internetservice/services/lookup/autocomplete/json?query='.urlencode($char));
        $elements = json_decode($json, 1);
        foreach($elements as $element) {
            if($element['type'] == 'divider') continue;
            if($element['type'] == 'route') continue;
            if($element['type'] != 'stop') {
                throw new Exception('Unknown element: '.var_export($element, 1));
            }
            $stops[$element['id']] = strtr($element['name'], $replacements);
        }
    }
}
asort($stops);
var_export($stops);
stops/populate_db.php
New file
@@ -0,0 +1,27 @@
<?php
if(php_sapi_name() !== 'cli') die();
include('stops.php');
include('common.php');
$pdo = new PDO('sqlite:stops_temp.db');
$pdo->query('DROP TABLE IF EXISTS stop_search');
$pdo->query('CREATE TABLE stop_search (
    word VARCHAR(60),
    id INT
)');
$pdo->beginTransaction();
$st = $pdo->prepare('INSERT INTO stop_search (word, id) VALUES (?, ?)');
foreach($stops as $id => $name) {
    foreach(split_stop_name($name) as $word) {
        $st->execute(array($word, $id));
        $st->closeCursor();
    }
}
$pdo->commit();
$pdo->query('CREATE INDEX stop_search_word ON stop_search (word COLLATE NOCASE)');
rename('stops_temp.db', 'stops.db');
stops/stops.db
Binary files differ
stops/stops.php
New file
@@ -0,0 +1,203 @@
<?php
$stops = array (
  113  => 'AWF',
  462  => 'Agencja Kraków Wschód',
# 134  => 'Balicka Wiadukt', # nowa nazwa # 136  => 'Bronowice Wiadukt',
  449  => 'Bardosa',
# 75   => 'Basztowa LOT', # nowa nazwa # 3032 => 'Stary Kleparz',
  78   => 'Batorego',
  130  => 'Białucha',
  867  => 'Bieńczycka',
  630  => 'Bieżanowska',
  84   => 'Biprostal',
  461  => 'Blokowa',
# 2798 => 'Boisko Kabel', # dawny tymczasowy
  747  => 'Borek Fałęcki',
  824  => 'Borek Fałęcki I',
  612  => 'Borsucza',
  451  => 'Brama nr 4',
  453  => 'Brama nr 5',
  61   => 'Bratysławska',
  89   => 'Bronowice',
  135  => 'Bronowice Małe',
  136  => 'Bronowice Wiadukt',
  613  => 'Brożka',
  409  => 'Centralna',
  3039 => 'Centrum Kongresowe ICE',
  2691 => 'Chmieleniec',
  87   => 'Cichy Kącik',
  3037 => 'Cienista', # nieczynny
# 2549 => 'Cmentarz Grębałów Zachód', # nowa nazwa # 2685 => 'Jarzębiny',
  621  => 'Cmentarz Podgórski',
  124  => 'Cmentarz Rakowicki',
  318  => 'Cracovia',
  129  => 'Cystersów',
  3038 => 'Czerwone Maki P+R',
  407  => 'Czyżyny',
  392  => 'DH Wanda',
  915  => 'Dajwór',
  435  => 'Darwina',
  632  => 'Dauna',
  388  => 'Dunikowskiego',
  623  => 'Dworcowa',
  131  => 'Dworzec Główny',
  1173 => 'Dworzec Główny Tunel',
# 8567 => 'Dworzec Główny Tunel', # duplikat # 1173 => 'Dworzec Główny Tunel',
  2608 => 'Dworzec Główny Zachód',
  2870 => 'Dworzec Płaszów Estakada',
  70   => 'Dworzec Towarowy',
# 6685 => 'Dworzec Towarowy', # duplikat # 70   => 'Dworzec Towarowy',
  370  => 'Dąbie',
  464  => 'Elektromontaż',
  368  => 'Fabryczna',
  322  => 'Filharmonia',
  1051 => 'Fort Mogiła', # nieczynny
  367  => 'Francesco Nullo',
  560  => 'Gromadzka',
# 585  => 'Grota Roweckiego', # nowa nazwa # 2687 => 'Grota-Roweckiego',
  2687 => 'Grota-Roweckiego',
  1049 => 'Głowackiego',
  363  => 'Hala Targowa',
# 6990 => 'Hala Targowa', # duplikat # 363  => 'Hala Targowa',
  2685 => 'Jarzębiny',
  452  => 'Jeżynowa',
  319  => 'Jubilat',
  624  => 'Kabel',
  2690 => 'Kampus UJ',
  576  => 'Kapelanka',
  429  => 'Klasztorna',
  382  => 'Kleeberga',
  946  => 'Klimeckiego',
  584  => 'Kobierzyńska',
# 401  => 'Kocmyrzowska', # nowa nazwa # 3037 => 'Cienista',
  457  => 'Koksochemia',
  459  => 'Kombinat',
  313  => 'Komorowskiego',
  450  => 'Kopiec Wandy',
# 2536 => 'Kordylewskiego', # nowa nazwa # 2859 => 'Teatr Variété',
  571  => 'Korona',
# 2803 => 'Kraków Arena Al. Pokoju', # nowa nazwa # 2871 => 'TAURON Arena Kraków Al. Pokoju',
# 959  => 'Kraków Plaza', # nowa nazwa # 3033 => 'Plaza',
  63   => 'Krowodrza Górka',
# 7612 => 'Krowodrza Górka', # duplikat # 63   => 'Krowodrza Górka',
  567  => 'Kuklińskiego',
  744  => 'Kurdwanów',
# 7389 => 'Kurdwanów pętla', # nowa nazwa # 744  => 'Kurdwanów',
# 2537 => 'Lema', # nowa nazwa # 2803 => 'Kraków Arena Al. Pokoju'
  569  => 'Limanowskiego',
  2686 => 'Lipińskiego',
  561  => 'Lipska',
  126  => 'Lubicz',
  930  => 'M1 Al. Pokoju',
  1263 => 'Mały Płaszów',
  454  => 'Meksyk',
  362  => 'Miodowa',
# 6989 => 'Miodowa', # duplikat # 362  => 'Miodowa',
  375  => 'Mistrzejowice',
  2538 => 'Miśnieńska',
# 574  => 'Most Grunwaldzki', # nowa nazwa # 3039 => 'Centrum Kongresowe ICE',
  460  => 'Mrozowa',
  2726 => 'Muzeum Inżynierii Miejskiej',
  2811 => 'Muzeum Lotnictwa',
  2688 => 'Norymberska',
# 372  => 'Nowohucka', # nowa nazwa # 3041 => 'Rondo 308. Dywizjonu',
  715  => 'Nowosądecka',
  2580 => 'Nowy Bieżanów',
  71   => 'Nowy Kleparz',
  2582 => 'Nowy Prokocim',
  369  => 'Ofiar Dąbia',
  823  => 'Oleandry',
  361  => 'Orzeszkowej',
  413  => 'Os.Kolorowe',
  424  => 'Os.Na Skarpie',
  378  => 'Os.Piastów',
  418  => 'Os.Zgody',
  377  => 'Os.Złotego Wieku',
  466  => 'PH',
  614  => 'PT',
  960  => 'Park Jordana',
  716  => 'Piaski Nowe',
  379  => 'Piasta Kołodzieja',
  570  => 'Plac Bohaterów Getta',
# 7207 => 'Plac Bohaterów Getta', # duplikat # 570  => 'Plac Bohaterów Getta',
# 414  => 'Plac Centralny', # nowa nazwa # 2744 => 'Plac Centralny im. R.Reagana',
  2744 => 'Plac Centralny im. R.Reagana',
  79   => 'Plac Inwalidów',
  360  => 'Plac Wolnica',
  1360 => 'Plac Wszystkich Świętych',
  3033 => 'Plaza',
  458  => 'Pleszów',
  357  => 'Poczta Główna',
  73   => 'Politechnika',
# 6689 => 'Politechnika', # duplikat # 73   => 'Politechnika',
  568  => 'Powstańców Wielkopolskich',
# 7204 => 'Powstańców Wielkopolskich', # duplikat # 568  => 'Powstańców Wielkopolskich',
  637  => 'Prokocim',
# 7279 => 'Prokocim', # duplikat # 637  => 'Prokocim',
  682  => 'Prokocim Szpital',
# 69   => 'Prądnicka', # nowa nazwa # 3036 => 'Szpital Narutowicza',
# 6684 => 'Prądnicka', # duplikat # 69   => 'Prądnicka',
  72   => 'Pędzichów',
  128  => 'Rakowicka',
  320  => 'Reymana',
  3041 => 'Rondo 308. Dywizjonu',
  408  => 'Rondo Czyżyńskie',
  365  => 'Rondo Grzegórzeckie',
# 6992 => 'Rondo Grzegórzeckie', # duplikat # 365  => 'Rondo Grzegórzeckie',
  2539 => 'Rondo Hipokratesa',
# 419  => 'Rondo Kocmyrzowskie', # nowa nazwa # 2745 => 'Rondo Kocmyrzowskie im. Ks. Gorzelanego',
  2745 => 'Rondo Kocmyrzowskie im. Ks. Gorzelanego',
  610  => 'Rondo Matecznego',
  125  => 'Rondo Mogilskie',
# 6747 => 'Rondo Mogilskie', # duplikat # 125  => 'Rondo Mogilskie',
  383  => 'Rondo Piastowskie',
# 587  => 'Rostworowskiego', # nowa nazwa # 589  => 'Ruczaj',
  589  => 'Ruczaj',
# 586  => 'Ruczaj I', # nowa nazwa # 585  => 'Grota Roweckiego',
  1262 => 'Rzebika',
  611  => 'Rzemieślnicza',
  311  => 'Salwator',
  615  => 'Sanktuarium Bożego Miłosierdzia',
  572  => 'Smolki',
  746  => 'Solvay',
  358  => 'Starowiślna',
# 6985 => 'Starowiślna', # duplikat # 358  => 'Starowiślna',
  3032 => 'Stary Kleparz',
  112  => 'Stella-Sawickiego',
  359  => 'Stradom',
  423  => 'Struga',
  2548 => 'Suche Stawy',
  3036 => 'Szpital Narutowicza',
  575  => 'Szwedzka',
  577  => 'Słomiana',
  2871 => 'TAURON Arena Kraków Al. Pokoju',
  3040 => 'TAURON Arena Kraków Wieczysta',
  77   => 'Teatr Bagatela',
  420  => 'Teatr Ludowy', # nieczynny
  2859 => 'Teatr Variété',
  681  => 'Teligi',
  127  => 'Uniwersytet Ekonomiczny',
  321  => 'Uniwersytet Jagielloński',
  88   => 'Uniwersytet Pedagogiczny',
  83   => 'Urzędnicza',
  463  => 'Walcownia',
  325  => 'Wawel',
  2543 => 'Wańkowicza', # nieczynny
  133  => 'Wesele',
  434  => 'Wiadukty',
# 114  => 'Wieczysta', # nowa nazwa # 3040 => 'TAURON Arena Kraków Wieczysta',
  718  => 'Witosa',
# 7362 => 'Witosa', # duplikat # 718  => 'Witosa',
  634  => 'Wlotowa',
  1154 => 'Zabłocie',
  465  => 'Zajezdnia Nowa Huta',
  679  => 'Ćwiklińskiej',
  922  => 'Łagiewniki',
# 7584 => 'Łagiewniki', # duplikat # 922  => 'Łagiewniki',
  2821 => 'Łagiewniki ZUS',
# 324  => 'Św. Gertrudy', # duplikat # 2741 => 'Św.Gertrudy',
# 364  => 'Św. Wawrzyńca', # duplikat # 2742 => 'Św.Wawrzyńca',
# 8508 => 'Św. Wawrzyńca', # duplikat # 364  => 'Św. Wawrzyńca',
  2741 => 'Św.Gertrudy',
  2742 => 'Św.Wawrzyńca',
);