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 = [
+	'&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);
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