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