From ced30990541a35ded43912901ba67c9a4b30ed5a Mon Sep 17 00:00:00 2001 From: Jacek Kowalski <Jacek@jacekk.info> Date: Fri, 17 Mar 2017 00:26:26 +0000 Subject: [PATCH] Use local database for stop name autocompletion --- stops/common.php | 17 ++ index.js | 6 stops/download_stops.php | 33 +++++ stops/populate_db.php | 27 ++++ stops.php | 70 +++++++++++ stops/stops.php | 203 +++++++++++++++++++++++++++++++++ stops/stops.db | 0 7 files changed, 353 insertions(+), 3 deletions(-) diff --git a/index.js b/index.js index dfbdf7c..8d2d8fe 100644 --- a/index.js +++ b/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; diff --git a/stops.php b/stops.php new file mode 100644 index 0000000..b9b0132 --- /dev/null +++ b/stops.php @@ -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(); +} diff --git a/stops/common.php b/stops/common.php new file mode 100644 index 0000000..0d6c3cf --- /dev/null +++ b/stops/common.php @@ -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; +} diff --git a/stops/download_stops.php b/stops/download_stops.php new file mode 100644 index 0000000..e5be954 --- /dev/null +++ b/stops/download_stops.php @@ -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 = [ + 'Ó' => 'Ó', + 'ó' => 'ó', + 'É' => 'É', + 'é' => 'é', +]; + +$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); diff --git a/stops/populate_db.php b/stops/populate_db.php new file mode 100644 index 0000000..3364d2d --- /dev/null +++ b/stops/populate_db.php @@ -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'); diff --git a/stops/stops.db b/stops/stops.db new file mode 100644 index 0000000..5a2dc15 --- /dev/null +++ b/stops/stops.db Binary files differ diff --git a/stops/stops.php b/stops/stops.php new file mode 100644 index 0000000..28d2907 --- /dev/null +++ b/stops/stops.php @@ -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', +); -- Gitblit v1.9.1