Bimba.git

commit 7ecdb479f437402005a9a1e3fdfefd6141f8659d

Author: Adam Pioterek <adam.pioterek@protonmail.ch>

raw gtfs but still too slow

%!v(PANIC=String method: strings: negative Repeat count)


diff --git a/app/build.gradle b/app/build.gradle
index 89b94b1a503c82e67b6c323dbb19478ae67285c2..d7b28bff18fb3049628861a5b2e676f896faa9a4 100644
--- a/app/build.gradle
+++ b/app/build.gradle
@@ -36,20 +36,14 @@     implementation "org.jetbrains.kotlin:kotlin-stdlib:$kotlin_version"
     implementation 'com.github.arimorty:floatingsearchview:2.1.1'
     implementation 'com.google.code.gson:gson:2.8.1'
     implementation 'com.squareup.okhttp3:okhttp:3.8.1'
-    implementation 'de.siegmar:fastcsv:1.0.2'
     implementation 'com.github.ghost1372:Mzip-Android:0.4.0'
+    implementation 'net.sf.supercsv:super-csv:2.4.0'
+    implementation 'io.requery:sqlite-android:3.22.0'
 }
 repositories {
     maven { url "https://maven.google.com" }
     maven { url 'https://jitpack.io' }
     mavenCentral()
 }
-
-/*configurations {
-    all {
-        exclude module: 'httpclient'
-        exclude module: 'commons-logging'
-    }
-}*/
 
 apply plugin: 'kotlin-android-extensions'




diff --git a/app/src/main/java/ml/adamsprogs/bimba/activities/DashActivity.kt b/app/src/main/java/ml/adamsprogs/bimba/activities/DashActivity.kt
index 6c743b8b0cc19a297ac05a122c8c6343ec37d00c..ad34903559b2740b8cda9f7e594660d7f5aa52ad 100644
--- a/app/src/main/java/ml/adamsprogs/bimba/activities/DashActivity.kt
+++ b/app/src/main/java/ml/adamsprogs/bimba/activities/DashActivity.kt
@@ -40,11 +40,15 @@
     override fun onCreate(savedInstanceState: Bundle?) {
         super.onCreate(savedInstanceState)
         setContentView(R.layout.activity_dash)
+
+        println("view set")
         AppCompatDelegate.setDefaultNightMode(AppCompatDelegate.MODE_NIGHT_AUTO)
         setSupportActionBar(toolbar)
         supportActionBar?.title = getString(R.string.merge_favourites)
+        println("getting stops")
 
         getStops()
+        println("stops got")
 
         prepareFavourites()
 




diff --git a/app/src/main/java/ml/adamsprogs/bimba/activities/SplashActivity.kt b/app/src/main/java/ml/adamsprogs/bimba/activities/SplashActivity.kt
index 7e1461e083061f66984121d839e3641294f31de2..04da7ada55bdb13f87789f6fec5dccf0a11e118f 100644
--- a/app/src/main/java/ml/adamsprogs/bimba/activities/SplashActivity.kt
+++ b/app/src/main/java/ml/adamsprogs/bimba/activities/SplashActivity.kt
@@ -3,6 +3,7 @@
 import android.support.v7.app.AppCompatActivity
 import android.os.Bundle
 import android.content.Intent
+import android.database.sqlite.SQLiteCantOpenDatabaseException
 import ml.adamsprogs.bimba.models.Timetable
 import java.io.FileNotFoundException
 
@@ -17,7 +18,7 @@             if (timetable.isEmpty())
                 startActivity(Intent(this, NoDbActivity::class.java))
             else
                 startActivity(Intent(this, DashActivity::class.java))
-        } catch(e: FileNotFoundException) {
+        } catch(e: Exception) {
             startActivity(Intent(this, NoDbActivity::class.java))
         }
         finish()




diff --git a/app/src/main/java/ml/adamsprogs/bimba/datasources/TimetableConverter.kt b/app/src/main/java/ml/adamsprogs/bimba/datasources/TimetableConverter.kt
index c5062388b6ba89e806378e681282d2ea5e554e6f..eba5e37a707473e4c39b73e943c15e5ea10e44b0 100644
--- a/app/src/main/java/ml/adamsprogs/bimba/datasources/TimetableConverter.kt
+++ b/app/src/main/java/ml/adamsprogs/bimba/datasources/TimetableConverter.kt
@@ -1,44 +1,69 @@
 package ml.adamsprogs.bimba.datasources
 
+import android.content.ContentValues
 import android.content.Context
-import android.database.sqlite.SQLiteDatabase
+import io.requery.android.database.sqlite.SQLiteDatabase
 import java.io.File
-import de.siegmar.fastcsv.reader.CsvRow
-import de.siegmar.fastcsv.reader.CsvReader
 import ir.mahdi.mzip.zip.ZipArchive
-import java.nio.charset.StandardCharsets
+import org.supercsv.cellprocessor.ift.CellProcessor
+import org.supercsv.prefs.CsvPreference
+import org.supercsv.io.CsvMapReader
+import org.supercsv.io.ICsvMapReader
+import java.io.FileReader
+import java.util.*
 
-
-class TimetableConverter(from: File, to: File, context: Context) {
-    private val db: SQLiteDatabase = SQLiteDatabase.openOrCreateDatabase(to, null)
-
+//todo faster csv: http://simpleflatmapper.org/0101-getting-started-csv.html
+//todo faster csv: https://github.com/uniVocity/univocity-parsers
+class TimetableConverter(from: File, context: Context) {
     init {
+       // println(Calendar.getInstance())
         val target = File(context.filesDir, "gtfs_files")
         target.mkdir()
         ZipArchive.unzip(from.path, target.path, "")
+        /*println("tables…")
         createTables()
+        println(" done")
+        println("agency…")
         insertAgency(context)
+        println(" done")
+        println("calendar…")
         insertCalendar(context)
+        println(" done")
+        println("dates…")
         insertCalendarDates(context)
+        println(" done")
+        println("feed…")
         insertFeedInfo(context)
+        println(" done")
+        println("routes…")
         insertRoutes(context)
+        println(" done")
+        println("shapes…")
         insertShapes(context)
+        println(" done")
+        println("stops…")
         insertStops(context)
+        println(" done")
+        println("times…")
         insertStopTimes(context)
+        println(" done")
+        println("trips…")
         insertTrips(context)
+        println(" done")
         target.deleteRecursively()
-    }
+        println(Calendar.getInstance())*/
+    } /*
 
     private fun createTables() {
-        db.rawQuery("create table agency(" +
+        db.execSQL("create table agency(" +
                 "agency_id TEXT PRIMARY KEY," +
                 "agency_name TEXT," +
                 "agency_url TEXT," +
                 "agency_timezone TEXT," +
                 "agency_phone TEXT," +
                 "agency_lang TEXT" +
-                ")", null)
-        db.rawQuery("create table calendar(" +
+                ")")
+        db.execSQL("create table calendar(" +
                 "service_id TEXT PRIMARY KEY," +
                 "monday INT," +
                 "tuesday INT," +
@@ -49,21 +74,21 @@                 "saturday INT," +
                 "sunday INT," +
                 "start_date TEXT," +
                 "end_date TEXT" +
-                ")", null)
-        db.rawQuery("create table calendar_dates(" +
+                ")")
+        db.execSQL("create table calendar_dates(" +
                 "service_id TEXT," +
                 "date TEXT," +
                 "exception_type INT," +
                 "FOREIGN KEY(service_id) REFERENCES calendar(service_id)" +
-                ")", null)
-        db.rawQuery("create table feed_info(" +
+                ")")
+        db.execSQL("create table feed_info(" +
                 "feed_publisher_name TEXT PRIMARY KEY," +
                 "feed_publisher_url TEXT," +
                 "feed_lang TEXT," +
                 "feed_start_date TEXT," +
                 "feed_end_date TEXT" +
-                ")", null)
-        db.rawQuery("create table routes(" +
+                ")")
+        db.execSQL("create table routes(" +
                 "route_id TEXT PRIMARY KEY," +
                 "agency_id TEXT," +
                 "route_short_name TEXT," +
@@ -73,23 +98,23 @@                 "route_type INT," +
                 "route_color TEXT," +
                 "route_text_color TEXT," +
                 "FOREIGN KEY(agency_id) REFERENCES agency(agency_id)" +
-                ")", null)
-        db.rawQuery("create table shapes(" +
-                "shape_id TEXT PRIMARY KEY," +
+                ")")
+        db.execSQL("create table shapes(" +
+                "shape_id TEXT," +
                 "shape_pt_lat DOUBLE," +
                 "shape_pt_lon DOUBLE," +
                 "shape_pt_sequence INT" +
-                ")", null)
-        db.rawQuery("create table stops" +
+                ")")
+        db.execSQL("create table stops(" +
                 "stop_id TEXT PRIMARY KEY," +
                 "stop_code TEXT," +
                 "stop_name TEXT," +
                 "stop_lat DOUBLE," +
                 "stop_lon DOUBLE," +
                 "zone_id TEXT" +
-                ")", null)
-        db.rawQuery("create table stop_times(" +
-                "trip_id TEXT PRIMARY KEY," +
+                ")")
+        db.execSQL("create table stop_times(" +
+                "trip_id TEXT," +
                 "arrival_time TEXT," +
                 "departure_time TEXT," +
                 "stop_id TEXT," +
@@ -98,8 +123,8 @@                 "stop_headsign TEXT," +
                 "pickup_type INT," +
                 "drop_off_type INT," +
                 "FOREIGN KEY(stop_id) REFERENCES stops(stop_id)" +
-                ")", null)
-        db.rawQuery("create table trips(" +
+                ")")
+        db.execSQL("create table trips(" +
                 "route_id TEXT," +
                 "service_id TEXT," +
                 "trip_id TEXT PRIMARY KEY," +
@@ -109,191 +134,286 @@                 "shape_id TEXT," +
                 "wheelchair_accessible INT," +
                 "FOREIGN KEY(route_id) REFERENCES routes(route_id)," +
                 "FOREIGN KEY(service_id) REFERENCES calendar(service_id)," +
-                "FOREIGN KEY(shape_id) REFERENCE shapes(shape_id)" +
-                ")", null)
+                "FOREIGN KEY(shape_id) REFERENCES shapes(shape_id)" +
+                ")")
     }
 
     private fun insertAgency(context: Context) {
         val file = File(context.filesDir, "gtfs_files/agency.txt")
-        val csvReader = CsvReader()
-        csvReader.setContainsHeader(true)
+        var mapReader: ICsvMapReader? = null
+        try {
+            db.beginTransaction()
+            mapReader = CsvMapReader(FileReader(file), CsvPreference.STANDARD_PREFERENCE)
+            val header = mapReader.getHeader(true)
 
-        csvReader.parse(file, StandardCharsets.UTF_8).use {
-            var row: CsvRow? = null
-            while ({ row = it.nextRow(); row }() != null) {
-                val id = row!!.getField("agency_id")
-                val name = row!!.getField("agency_name")
-                val url = row!!.getField("agency_url")
-                val timezone = row!!.getField("agency_timezone")
-                val phone = row!!.getField("agency_phone")
-                val lang = row!!.getField("agency_lang")
-                db.rawQuery("insert into agency values(?, ?, ?, ?, ?, ?)",
-                        arrayOf(id, name, url, timezone, phone, lang))
+            var customerMap: Map<String, Any>? = null
+            val processors = Array<CellProcessor?>(header.size, { null })
+            while ({ customerMap = mapReader.read(header, processors); customerMap }() != null) {
+                val values = ContentValues().apply {
+                    put("agency_id", customerMap!!["agency_id"] as String)
+                    put("agency_name", customerMap!!["agency_name"] as String)
+                    put("agency_url", customerMap!!["agency_url"] as String)
+                    put("agency_timezone", customerMap!!["agency_timezone"] as String)
+                    put("agency_phone", customerMap!!["agency_phone"] as String)
+                    put("agency_lang", customerMap!!["agency_lang"] as String)
+                }
+                db.insert("agency", null, values)
+            }
+            db.setTransactionSuccessful()
+            db.endTransaction()
+        } finally {
+            if (mapReader != null) {
+                mapReader.close()
             }
         }
     }
 
     private fun insertCalendar(context: Context) {
         val file = File(context.filesDir, "gtfs_files/calendar.txt")
-        val csvReader = CsvReader()
-        csvReader.setContainsHeader(true)
+        var mapReader: ICsvMapReader? = null
+        try {
+            db.beginTransaction()
+            mapReader = CsvMapReader(FileReader(file), CsvPreference.STANDARD_PREFERENCE)
+            val header = mapReader.getHeader(true)
 
-        csvReader.parse(file, StandardCharsets.UTF_8).use {
-            var row: CsvRow? = null
-            while ({ row = it.nextRow(); row }() != null) {
-                val serviceId = row!!.getField("service_id")
-                val monday = row!!.getField("monday")
-                val tuesday = row!!.getField("tuesday")
-                val wednesday = row!!.getField("wednesday")
-                val thursday = row!!.getField("thursday")
-                val friday = row!!.getField("friday")
-                val saturday = row!!.getField("saturday")
-                val sunday = row!!.getField("sunday")
-                val startDate = row!!.getField("start_date")
-                val endDate = row!!.getField("end_date")
-                db.rawQuery("insert into calendar values(?, ?, ?, ?, ?, ?, ?, ?, ?, ?)",
-                        arrayOf(serviceId, monday, tuesday, wednesday, thursday, friday, saturday,
-                                sunday, startDate, endDate))
+            var customerMap: Map<String, Any>? = null
+            val processors = Array<CellProcessor?>(header.size, { null })
+            while ({ customerMap = mapReader.read(header, processors); customerMap }() != null) {
+                val values = ContentValues().apply {
+                    put("service_id", customerMap!!["service_id"] as String)
+                    put("monday", customerMap!!["monday"] as String)
+                    put("tuesday", customerMap!!["tuesday"] as String)
+                    put("wednesday", customerMap!!["wednesday"] as String)
+                    put("thursday", customerMap!!["thursday"] as String)
+                    put("friday", customerMap!!["friday"] as String)
+                    put("saturday", customerMap!!["saturday"] as String)
+                    put("sunday", customerMap!!["sunday"] as String)
+                    put("start_date", customerMap!!["start_date"] as String)
+                    put("end_date", customerMap!!["end_date"] as String)
+                }
+
+                db.insert("calendar", null, values)
+            }
+            db.setTransactionSuccessful()
+            db.endTransaction()
+        } finally {
+            if (mapReader != null) {
+                mapReader.close()
             }
         }
     }
 
     private fun insertCalendarDates(context: Context) {
         val file = File(context.filesDir, "gtfs_files/calendar_dates.txt")
-        val csvReader = CsvReader()
-        csvReader.setContainsHeader(true)
+
+        var mapReader: ICsvMapReader? = null
+        try {
+            db.beginTransaction()
+            mapReader = CsvMapReader(FileReader(file), CsvPreference.STANDARD_PREFERENCE)
+            val header = mapReader.getHeader(true)
 
-        csvReader.parse(file, StandardCharsets.UTF_8).use {
-            var row: CsvRow? = null
-            while ({ row = it.nextRow(); row }() != null) {
-                val serviceId = row!!.getField("service_id")
-                val date = row!!.getField("date")
-                val exceptionType = row!!.getField("exceptionType")
-                db.rawQuery("insert into calendar_dates values(?, ?, ?)",
-                        arrayOf(serviceId, date, exceptionType))
+            var customerMap: Map<String, Any>? = null
+            val processors = Array<CellProcessor?>(header.size, { null })
+            while ({ customerMap = mapReader.read(header, processors); customerMap }() != null) {
+                val values = ContentValues().apply {
+                    put("service_id", customerMap!!["service_id"] as String)
+                    put("date", customerMap!!["date"] as String)
+                    put("exceptionType", customerMap!!["exceptionType"] as String)
+                }
+                db.insert("calendar_dates", null, values)
+            }
+            db.setTransactionSuccessful()
+            db.endTransaction()
+        } finally {
+            if (mapReader != null) {
+                mapReader.close()
             }
         }
     }
 
     private fun insertFeedInfo(context: Context) {
         val file = File(context.filesDir, "gtfs_files/feed_info.txt")
-        val csvReader = CsvReader()
-        csvReader.setContainsHeader(true)
+        var mapReader: ICsvMapReader? = null
+        try {
+            db.beginTransaction()
+            mapReader = CsvMapReader(FileReader(file), CsvPreference.STANDARD_PREFERENCE)
+            val header = mapReader.getHeader(true)
 
-        csvReader.parse(file, StandardCharsets.UTF_8).use {
-            var row: CsvRow? = null
-            while ({ row = it.nextRow(); row }() != null) {
-                val name = row!!.getField("feed_publisher_name")
-                val url = row!!.getField("feed_publisher_url")
-                val lang = row!!.getField("feed_lang")
-                val startDate = row!!.getField("feed_start_date")
-                val endDate = row!!.getField("feed_end_date")
-                db.rawQuery("insert into feed_info values(?, ?, ?, ?, ?)",
-                        arrayOf(name, url, lang, startDate, endDate))
+            var customerMap: Map<String, Any>? = null
+            val processors = Array<CellProcessor?>(header.size, { null })
+            while ({ customerMap = mapReader.read(header, processors); customerMap }() != null) {
+                val values = ContentValues().apply {
+                    put("feed_publisher_name", customerMap!!["feed_publisher_name"] as String)
+                    put("feed_publisher_url", customerMap!!["feed_publisher_url"] as String)
+                    put("feed_lang", customerMap!!["feed_lang"] as String)
+                    put("feed_start_date", customerMap!!["feed_start_date"] as String)
+                    put("feed_end_date", customerMap!!["feed_end_date"] as String)
+                }
+                db.insert("feed_info", null, values)
+            }
+            db.setTransactionSuccessful()
+            db.endTransaction()
+        } finally {
+            if (mapReader != null) {
+                mapReader.close()
             }
         }
     }
 
     private fun insertRoutes(context: Context) {
         val file = File(context.filesDir, "gtfs_files/routes.txt")
-        val csvReader = CsvReader()
-        csvReader.setContainsHeader(true)
+        var mapReader: ICsvMapReader? = null
+        try {
+            db.beginTransaction()
+            mapReader = CsvMapReader(FileReader(file), CsvPreference.STANDARD_PREFERENCE)
+            val header = mapReader.getHeader(true)
 
-        csvReader.parse(file, StandardCharsets.UTF_8).use {
-            var row: CsvRow? = null
-            while ({ row = it.nextRow(); row }() != null) {
-                val id = row!!.getField("route_id")
-                val agencyId = row!!.getField("agency_id")
-                val shortName = row!!.getField("route_short_name")
-                val longName = row!!.getField("route_long_name")
-                val description = row!!.getField("route_desc")
-                val type = row!!.getField("route_type")
-                val colour = row!!.getField("route_color")
-                val textColour = row!!.getField("route_text_color")
-                db.rawQuery("insert into routes values(?, ?, ?, ?, ?, ?, ?, ?)",
-                        arrayOf(id, agencyId, shortName, longName, description, type, colour,
-                                textColour))
+            var customerMap: Map<String, Any>? = null
+            val processors = Array<CellProcessor?>(header.size, { null })
+            while ({ customerMap = mapReader.read(header, processors); customerMap }() != null) {
+                val values = ContentValues().apply {
+                    put("route_id", customerMap!!["route_id"] as String)
+                    put("agency_id", customerMap!!["agency_id"] as String)
+                    put("route_short_name", customerMap!!["route_short_name"] as String)
+                    put("route_long_name", customerMap!!["route_long_name"] as String)
+                    put("route_desc", customerMap!!["route_desc"] as String)
+                    put("route_type", customerMap!!["route_type"] as String)
+                    put("route_color", customerMap!!["route_color"] as String)
+                    put("route_text_color", customerMap!!["route_text_color"] as String)
+                }
+                db.insert("routes", null, values)
+            }
+            db.setTransactionSuccessful()
+            db.endTransaction()
+        } finally {
+            if (mapReader != null) {
+                mapReader.close()
             }
         }
     }
 
     private fun insertShapes(context: Context) {
         val file = File(context.filesDir, "gtfs_files/shapes.txt")
-        val csvReader = CsvReader()
-        csvReader.setContainsHeader(true)
+        var mapReader: ICsvMapReader? = null
+        try {
+            db.beginTransaction()
+            mapReader = CsvMapReader(FileReader(file), CsvPreference.STANDARD_PREFERENCE)
+            val header = mapReader.getHeader(true)
 
-        csvReader.parse(file, StandardCharsets.UTF_8).use {
-            var row: CsvRow? = null
-            while ({ row = it.nextRow(); row }() != null) {
-                val id = row!!.getField("shape_id")
-                val latitude = row!!.getField("shape_pt_lat")
-                val longitude = row!!.getField("shape_pt_lon")
-                val sequence = row!!.getField("shape_pt_sequence")
-                db.rawQuery("insert into shapes values(?, ?, ?, ?, ?)",
-                        arrayOf(id, latitude, longitude, sequence))
+            var customerMap: Map<String, Any>? = null
+            val processors = Array<CellProcessor?>(header.size, { null })
+            while ({ customerMap = mapReader.read(header, processors); customerMap }() != null) {
+                val values = ContentValues().apply {
+                    put("shape_id", customerMap!!["shape_id"] as String)
+                    put("shape_pt_lat", customerMap!!["shape_pt_lat"] as String)
+                    put("shape_pt_lon", customerMap!!["shape_pt_lon"] as String)
+                    put("shape_pt_sequence", customerMap!!["shape_pt_sequence"] as String)
+                }
+                db.insert("shapes", null, values)
+                if (mapReader.rowNumber % 1000 == 0)
+                    println(mapReader.rowNumber)
+            }
+            db.setTransactionSuccessful()
+            db.endTransaction()
+        } finally {
+            if (mapReader != null) {
+                mapReader.close()
             }
         }
     }
 
     private fun insertStops(context: Context) {
         val file = File(context.filesDir, "gtfs_files/stops.txt")
-        val csvReader = CsvReader()
-        csvReader.setContainsHeader(true)
+        var mapReader: ICsvMapReader? = null
+        try {
+            db.beginTransaction()
+            mapReader = CsvMapReader(FileReader(file), CsvPreference.STANDARD_PREFERENCE)
+            val header = mapReader.getHeader(true)
 
-        csvReader.parse(file, StandardCharsets.UTF_8).use {
-            var row: CsvRow? = null
-            while ({ row = it.nextRow(); row }() != null) {
-                val id = row!!.getField("stop_id")
-                val code = row!!.getField("stop_code")
-                val name = row!!.getField("stop_name")
-                val latitude = row!!.getField("stop_lat")
-                val longitude = row!!.getField("stop_lon")
-                val zone = row!!.getField("zone_id")
-                db.rawQuery("insert into stops values(?, ?, ?, ?, ?, ?)",
-                        arrayOf(id, code, name, latitude, longitude, zone))
+            var customerMap: Map<String, Any>? = null
+            val processors = Array<CellProcessor?>(header.size, { null })
+            while ({ customerMap = mapReader.read(header, processors); customerMap }() != null) {
+                val values = ContentValues().apply {
+                    put("stop_id", customerMap!!["stop_id"] as String)
+                    put("stop_code", customerMap!!["stop_code"] as String)
+                    put("stop_name", customerMap!!["stop_name"] as String)
+                    put("stop_lat", customerMap!!["stop_lat"] as String)
+                    put("stop_lon", customerMap!!["stop_lon"] as String)
+                    put("zone_id", customerMap!!["zone_id"] as String)
+                }
+                db.insert("stops", null, values)
+            }
+            db.setTransactionSuccessful()
+            db.endTransaction()
+        } finally {
+            if (mapReader != null) {
+                mapReader.close()
             }
         }
     }
 
     private fun insertStopTimes(context: Context) {
         val file = File(context.filesDir, "gtfs_files/stop_times.txt")
-        val csvReader = CsvReader()
-        csvReader.setContainsHeader(true)
+        var mapReader: ICsvMapReader? = null
+        try {
+            db.beginTransaction()
+            mapReader = CsvMapReader(FileReader(file), CsvPreference.STANDARD_PREFERENCE)
+            val header = mapReader.getHeader(true)
 
-        csvReader.parse(file, StandardCharsets.UTF_8).use {
-            var row: CsvRow? = null
-            while ({ row = it.nextRow(); row }() != null) {
-                val id = row!!.getField("trip_id")
-                val arrival = row!!.getField("arrival_time")
-                val departure = row!!.getField("departure_time")
-                val stop = row!!.getField("stop_id")
-                val sequence = row!!.getField("stop_sequence")
-                val headsign = row!!.getField("stop_headsign")
-                val pickup = row!!.getField("pickup_type")
-                val dropOff = row!!.getField("drop_off_type")
-                db.rawQuery("insert into stop_times values(?, ?, ?, ?, ?, ?, ?, ?)",
-                        arrayOf(id, arrival, departure, stop, sequence, headsign, pickup, dropOff))
+            var customerMap: Map<String, Any>? = null
+            val processors = Array<CellProcessor?>(header.size, { null })
+            while ({ customerMap = mapReader.read(header, processors); customerMap }() != null) {
+                val values = ContentValues().apply {
+                    put("trip_id", customerMap!!["trip_id"] as String)
+                    put("arrival_time", customerMap!!["arrival_time"] as String)
+                    put("departure_time", customerMap!!["departure_time"] as String)
+                    put("stop_id", customerMap!!["stop_id"] as String)
+                    put("stop_sequence", customerMap!!["stop_sequence"] as String)
+                    put("stop_headsign", customerMap!!["stop_headsign"] as String)
+                    put("pickup_type", customerMap!!["pickup_type"] as String)
+                    put("drop_off_type", customerMap!!["drop_off_type"] as String)
+                }
+                db.insert("stop_times", null, values)
+                if (mapReader.rowNumber % 10000 == 0)
+                    println(mapReader.rowNumber)
+            }
+            db.setTransactionSuccessful()
+            db.endTransaction()
+        } finally {
+            if (mapReader != null) {
+                mapReader.close()
             }
         }
     }
 
     private fun insertTrips(context: Context) {
         val file = File(context.filesDir, "gtfs_files/trips.txt")
-        val csvReader = CsvReader()
-        csvReader.setContainsHeader(true)
+        var mapReader: ICsvMapReader? = null
+        try {
+            db.beginTransaction()
+            mapReader = CsvMapReader(FileReader(file), CsvPreference.STANDARD_PREFERENCE)
+            val header = mapReader.getHeader(true)
 
-        csvReader.parse(file, StandardCharsets.UTF_8).use {
-            var row: CsvRow? = null
-            while ({ row = it.nextRow(); row }() != null) {
-                val route = row!!.getField("route_id")
-                val service = row!!.getField("service_id")
-                val id = row!!.getField("trip_id")
-                val headsign = row!!.getField("headsign")
-                val direction = row!!.getField("direction")
-                val shape = row!!.getField("shape")
-                db.rawQuery("insert into trpis values(?, ?, ?, ?, ?, ?)",
-                        arrayOf(route, service, id, headsign, direction, shape))
+            var customerMap: Map<String, Any>? = null
+            val processors = Array<CellProcessor?>(header.size, { null })
+            while ({ customerMap = mapReader.read(header, processors); customerMap }() != null) {
+                val values = ContentValues().apply {
+                    put("route_id", customerMap!!["route_id"] as String)
+                    put("service_id", customerMap!!["service_id"] as String)
+                    put("trip_id", customerMap!!["trip_id"] as String)
+                    put("trip_headsign", customerMap!!["trip_headsign"] as String)
+                    put("direction_id", customerMap!!["direction_id"] as String)
+                    put("shape_id", customerMap!!["shape_id"] as String)
+                    put("wheelchair_accessible", customerMap!!["wheelchair_accessible"] as String)
+                }
+                db.insert("trips", null, values)
+            }
+            db.setTransactionSuccessful()
+            db.endTransaction()
+        } finally {
+            if (mapReader != null) {
+                mapReader.close()
             }
         }
-    }
+    }*/
 }
\ No newline at end of file




diff --git a/app/src/main/java/ml/adamsprogs/bimba/datasources/TimetableDownloader.kt b/app/src/main/java/ml/adamsprogs/bimba/datasources/TimetableDownloader.kt
index e8b3232b47b94589d1a1a18fb68666998e7c695c..fc3101e6e7c0b3cc699dbc5f9715b08e3025afb9 100644
--- a/app/src/main/java/ml/adamsprogs/bimba/datasources/TimetableDownloader.kt
+++ b/app/src/main/java/ml/adamsprogs/bimba/datasources/TimetableDownloader.kt
@@ -12,6 +12,7 @@ import java.io.*
 import java.security.MessageDigest
 import android.app.NotificationManager
 import android.os.Build
+import ir.mahdi.mzip.zip.ZipArchive
 import ml.adamsprogs.bimba.NetworkStateReceiver
 import ml.adamsprogs.bimba.NotificationChannels
 import ml.adamsprogs.bimba.R
@@ -48,9 +49,11 @@                 sendResult(RESULT_NO_CONNECTIVITY)
                 return
             }
             val lastModified = httpCon.getHeaderField("Content-Disposition").split("=")[1].trim('\"').split("_")[0]
-            //todo size
+            size = httpCon.getHeaderField("Content-Length").toInt() / 1024
+
+            val force = intent.getBooleanExtra(EXTRA_FORCE, false)
             val currentLastModified = prefs.getString("timetableLastModified", "19791012")
-            if (lastModified <= currentLastModified && lastModified <= today()) {
+            if (lastModified <= currentLastModified && lastModified <= today() && !force) {
                 sendResult(RESULT_UP_TO_DATE)
                 return
             }
@@ -58,19 +61,25 @@
             notifyDownloading(0)
 
             val gtfs = File(this.filesDir, "timetable.zip")
-            val db = File(this.filesDir, "timetable.db")
+            //val db = File(this.filesDir, "timetable.db")
             copyInputStreamToFile(httpCon.inputStream, gtfs)
             val prefsEditor = prefs.edit()
             prefsEditor.putString("timetableLastModified", lastModified)
             prefsEditor.apply()
             sendResult(RESULT_DOWNLOADED)
 
-            notifyConverting()
+            //notifyConverting() //fixme
 
-            db.delete()
-            TimetableConverter(gtfs, File(this.filesDir, "timetable.db"), this)
+            //db.delete()
+            val target = File(this.filesDir, "gtfs_files")
+            target.deleteRecursively()
+            println("deleted")
+            target.mkdir()
+            ZipArchive.unzip(gtfs.path, target.path, "")
+            println("unzipped")
             gtfs.delete()
-            Timetable.getTimetable().refresh(this)
+            Timetable.getTimetable().refresh()
+            println("refreshed")
 
             cancelNotification()
 
@@ -81,7 +90,7 @@
     private fun today(): String {
         val cal = Calendar.getInstance()
         val d = cal[Calendar.DAY_OF_MONTH]
-        val m = cal[Calendar.MONTH]+1
+        val m = cal[Calendar.MONTH] + 1
         val y = cal[Calendar.YEAR]
 
         return "%d%02d%02d".format(y, m, d)




diff --git a/app/src/main/java/ml/adamsprogs/bimba/gtfs/AgencyAndId.kt b/app/src/main/java/ml/adamsprogs/bimba/gtfs/AgencyAndId.kt
index 88fba9e0999f624b6522e9f8d306e5c8aa556b86..f54736463c26017c29e5aff321a69dbd3b0ae99a 100644
--- a/app/src/main/java/ml/adamsprogs/bimba/gtfs/AgencyAndId.kt
+++ b/app/src/main/java/ml/adamsprogs/bimba/gtfs/AgencyAndId.kt
@@ -2,7 +2,11 @@ package ml.adamsprogs.bimba.gtfs
 
 import java.io.Serializable
 
-data class AgencyAndId(val id: String):Serializable {
+data class AgencyAndId(val id: String) : Serializable, Comparable<AgencyAndId> {
+    override fun compareTo(other: AgencyAndId): Int {
+        return this.toString().compareTo(other.toString())
+    }
+
     companion object {
         fun convertFromString(str: String): AgencyAndId {
             return AgencyAndId(str)




diff --git a/app/src/main/java/ml/adamsprogs/bimba/gtfs/Calendar.kt b/app/src/main/java/ml/adamsprogs/bimba/gtfs/Calendar.kt
index 4f9db75a45aefaf1944c5a6c4c1c00af3a543431..f1a7cd31e0c9b143e3d887a60718e5df0aec5ce8 100644
--- a/app/src/main/java/ml/adamsprogs/bimba/gtfs/Calendar.kt
+++ b/app/src/main/java/ml/adamsprogs/bimba/gtfs/Calendar.kt
@@ -1,4 +1,4 @@
 package ml.adamsprogs.bimba.gtfs
 
-data class Calendar(val monday: Int, val tuesday: Int, val wednesday: Int, val thursday: Int,
-                    val friday: Int, val saturday: Int, val sunday: Int)
\ No newline at end of file
+data class Calendar(val monday: Boolean, val tuesday: Boolean, val wednesday: Boolean, val thursday: Boolean,
+                    val friday: Boolean, val saturday: Boolean, val sunday: Boolean)
\ No newline at end of file




diff --git a/app/src/main/java/ml/adamsprogs/bimba/models/StopSuggestion.kt b/app/src/main/java/ml/adamsprogs/bimba/models/StopSuggestion.kt
index 1d2a383aa7b762814fe0db07a1f63373aee78389..3746e8ea1a2f6acc7c16f517a21f0eace3eb419a 100644
--- a/app/src/main/java/ml/adamsprogs/bimba/models/StopSuggestion.kt
+++ b/app/src/main/java/ml/adamsprogs/bimba/models/StopSuggestion.kt
@@ -5,7 +5,7 @@ import android.os.Parcelable
 import com.arlib.floatingsearchview.suggestions.model.SearchSuggestion
 import ml.adamsprogs.bimba.gtfs.AgencyAndId
 
-class StopSuggestion(private val directions: HashSet<String>, val id: AgencyAndId) : SearchSuggestion {
+class StopSuggestion(private val directions: Set<String>, val id: AgencyAndId) : SearchSuggestion {
 
     @Suppress("UNCHECKED_CAST")
     constructor(parcel: Parcel) : this(parcel.readSerializable() as HashSet<String>, parcel.readSerializable() as AgencyAndId)
@@ -15,7 +15,7 @@         return Parcelable.CONTENTS_FILE_DESCRIPTOR
     }
 
     override fun writeToParcel(dest: Parcel?, flags: Int) {
-        dest?.writeSerializable(directions)
+        dest?.writeSerializable(directions as HashSet)
         dest?.writeSerializable(id)
     }
 




diff --git a/app/src/main/java/ml/adamsprogs/bimba/models/Timetable.kt b/app/src/main/java/ml/adamsprogs/bimba/models/Timetable.kt
index 130b24089a12bbd0c001049115e4a197a97ddfcb..8627e2102c2f0ee359421cf9229e9d366deab2c7 100644
--- a/app/src/main/java/ml/adamsprogs/bimba/models/Timetable.kt
+++ b/app/src/main/java/ml/adamsprogs/bimba/models/Timetable.kt
@@ -1,32 +1,35 @@
 package ml.adamsprogs.bimba.models
 
 import android.content.Context
-import android.database.sqlite.SQLiteDatabase
 import ml.adamsprogs.bimba.datasources.CacheManager
 import ml.adamsprogs.bimba.gtfs.AgencyAndId
 import ml.adamsprogs.bimba.gtfs.Route
 import ml.adamsprogs.bimba.gtfs.Trip
 import ml.adamsprogs.bimba.gtfs.Calendar
 import ml.adamsprogs.bimba.secondsAfterMidnight
-import ml.adamsprogs.bimba.toPascalCase
+import org.supercsv.cellprocessor.ift.CellProcessor
+import org.supercsv.io.CsvMapReader
+import org.supercsv.prefs.CsvPreference
 import java.io.File
+import java.io.FileReader
 import kotlin.collections.ArrayList
 import kotlin.collections.HashMap
 import kotlin.collections.HashSet
 import java.util.Calendar as JCalendar
 
+//todo faster csv: http://simpleflatmapper.org/0101-getting-started-csv.html
+//todo faster csv: https://github.com/uniVocity/univocity-parsers
+//todo prolly need to write own simple and fast parser
 class Timetable private constructor() {
     companion object {
-        const val MODE_WORKDAYS = "workdays"
-        const val MODE_SATURDAYS = "saturdays"
-        const val MODE_SUNDAYS = "sundays"
         private var timetable: Timetable? = null
 
         fun getTimetable(context: Context? = null, force: Boolean = false): Timetable {
             return if (timetable == null || force)
                 if (context != null) {
                     timetable = Timetable()
-                    timetable!!.store = read(context)
+                    //timetable!!.store = read(context)
+                    timetable!!.filesDir = context.filesDir
                     timetable!!.cacheManager = CacheManager.getCacheManager(context)
                     timetable as Timetable
                 } else
@@ -35,18 +38,19 @@             else
                 timetable as Timetable
         }
 
-        private fun read(context: Context): SQLiteDatabase {
+        /*private fun read(context: Context): SQLiteDatabase {
             return SQLiteDatabase.openDatabase(File(context.filesDir, "timetable.db").path,
                     null, SQLiteDatabase.OPEN_READONLY)
-        }
+        }*/
     }
 
-    lateinit var store: SQLiteDatabase
+    //lateinit var store: SQLiteDatabase
     private lateinit var cacheManager: CacheManager
     private var _stops: ArrayList<StopSuggestion>? = null
+    private lateinit var filesDir: File
 
-    fun refresh(context: Context) {
-        this.store = read(context)
+    fun refresh() {
+        //this.store = read(context)
 
         cacheManager.recreate(getStopDeparturesByPlates(cacheManager.keys().toSet()))
 
@@ -54,6 +58,7 @@         getStops(true)
     }
 
     fun getStops(force: Boolean = false): List<StopSuggestion> {
+        println("STOPS!")
         if (_stops != null && !force)
             return _stops!!
 
@@ -74,55 +79,129 @@         AWF
         10 → Franowo, 29 → Franowo, 6 → Miłostowo, 5 → Stomil, 18 → Franowo, 15 → Franowo, 12 → Starołęka, 74 → Os. Orła Białego|8:4586|AWF73
         */
 
-        //trip_id, stop_id from stop_times if drop_off_type in {0,3}
+        //trip_id, stop_id from stop_times if pickup_type in {0,3}
         //route_id as line, trip_id, headsign from trips
-        //stop_id, stop_code from stops
+
+        println(JCalendar.getInstance())
+        val stopTripMap = HashMap<String, Set<String>>()
+        val stopTimesFile = File(filesDir, "gtfs_files/stop_times.txt")
+        var mapReader = CsvMapReader(FileReader(stopTimesFile), CsvPreference.STANDARD_PREFERENCE)
+        var header = mapReader.getHeader(true)
+
+        var stopTimesRow: Map<String, Any>? = null
+        var processors = Array<CellProcessor?>(header.size, { null })
+        while ({ stopTimesRow = mapReader.read(header, processors); stopTimesRow }() != null) {
+            if ((stopTimesRow!!["pickup_type"] as String) in arrayOf("0", "3")) {
+                val stopId = stopTimesRow!!["stop_id"] as String
+                val tripId = stopTimesRow!!["trip_id"] as String
+                if (stopId !in stopTripMap)
+                    stopTripMap[stopId] = HashSet()
+                (stopTripMap[stopId]!! as HashSet).add(tripId)
+            }
+        }
+        mapReader.close()
+        println(JCalendar.getInstance())
 
-        val map = HashMap<AgencyAndId, HashSet<String>>()
+        val tripIds = stopTripMap.flatMap { it.value }
 
-        val cursor = store.rawQuery("select route_short_name, trip_headsign, stop_id " +
-                "from stop_times join trips using trip_id join routes using route_id " +
-                "where drop_off_type = 0 or drop_off_type = 3", null)
+        val trips = HashMap<String, String>()
+        val tripsFile = File(filesDir, "gtfs_files/trips.txt")
+        mapReader = CsvMapReader(FileReader(tripsFile), CsvPreference.STANDARD_PREFERENCE)
+        header = mapReader.getHeader(true)
 
-        while(cursor.moveToNext()) {
-            val line = cursor.getString(0)
-            val headsign = cursor.getString(1).toPascalCase()
-            val stopId = AgencyAndId(cursor.getString(2))
-            if (map[stopId] == null)
-                map[stopId] = HashSet()
-            map[stopId]!!.add("$line → $headsign")
+        var tripsRow: Map<String, Any>? = null
+        processors = Array(header.size, { null })
+        while ({ tripsRow = mapReader.read(header, processors); tripsRow }() != null) { //fixme takes 16 min, 21 times more than a file 28 times bigger
+            val tripId = tripsRow!!["trip_id"] as String
+            if (tripId in tripIds) {
+                trips[tripId] = tripsRow!!["trip_headsign"] as String //todo save route_id
+            }
         }
+        mapReader.close()
+        println(JCalendar.getInstance())
 
-        cursor.close()
+        val routes = HashMap<String, String>()
+        val routesFile = File(filesDir, "gtfs_files/routes.txt")
+        mapReader = CsvMapReader(FileReader(routesFile), CsvPreference.STANDARD_PREFERENCE)
+        header = mapReader.getHeader(true)
+
+        var routesRow: Map<String, Any>? = null
+        processors = Array(header.size, { null })
+        while ({ routesRow = mapReader.read(header, processors); routesRow }() != null) {
+            val tripId = routesRow!!["route_id"] as String
+            if (tripId in tripIds) {//fixme
+                routes[tripId] = routesRow!!["route_short_name"] as String
+            }
+        }
+        mapReader.close()
+        println(JCalendar.getInstance())
+
+        val map = HashMap<AgencyAndId, Set<String>>()
+
+        stopTripMap.forEach {
+            val directions = HashSet<String>()
+            it.value.forEach {
+                val route = routes[it]
+                val headsign = trips[it]
+                directions.add("$route → $headsign")
+            }
+            map[AgencyAndId(it.key)] = directions
+        }
 
-        val stops = map.entries.map { StopSuggestion(it.value, it.key) }.toSet()
+        _stops = map.map { StopSuggestion(it.value, it.key) }.sortedBy { getStopName(it.id) } as ArrayList<StopSuggestion>
 
-        _stops = stops.sortedBy { this.getStopCode(it.id) } as ArrayList<StopSuggestion>
         return _stops!!
     }
 
     fun getStopName(stopId: AgencyAndId): String {
-        val cursor = store.rawQuery("select stop_name from stops where stop_id = ?", arrayOf(stopId.id))
-        cursor.moveToNext()
-        val name = cursor.getString(0)
-        cursor.close()
-        return name
+        val file = File(filesDir, "gtfs_files/stops.txt")
+        val mapReader = CsvMapReader(FileReader(file), CsvPreference.STANDARD_PREFERENCE)
+        val header = mapReader.getHeader(true)
+
+        var row: Map<String, Any>? = null
+        val processors = Array<CellProcessor?>(header.size, { null })
+        while ({ row = mapReader.read(header, processors); row }() != null) {
+            if ((row!!["stop_id"] as String) == stopId.id) {
+                mapReader.close()
+                return row!!["stop_name"] as String
+            }
+        }
+        mapReader.close()
+        throw IllegalArgumentException("Stop $stopId not in store")
     }
 
     fun getStopCode(stopId: AgencyAndId): String {
-        val cursor = store.rawQuery("select stop_code from stops where stop_id = ?", arrayOf(stopId.id))
-        cursor.moveToNext()
-        val name = cursor.getString(0)
-        cursor.close()
-        return name
+        val file = File(filesDir, "gtfs_files/stops.txt")
+        val mapReader = CsvMapReader(FileReader(file), CsvPreference.STANDARD_PREFERENCE)
+        val header = mapReader.getHeader(true)
+
+        var row: Map<String, Any>? = null
+        val processors = Array<CellProcessor?>(header.size, { null })
+        while ({ row = mapReader.read(header, processors); row }() != null) {
+            if ((row!!["stop_id"] as String) == stopId.id) {
+                mapReader.close()
+                return row!!["stop_code"] as String
+            }
+        }
+        mapReader.close()
+        throw IllegalArgumentException("Stop $stopId not in store")
     }
 
     fun getLineNumber(lineId: AgencyAndId): String {
-        val cursor = store.rawQuery("select route_short_name from routes where route_id = ?", arrayOf(lineId.id))
-        cursor.moveToNext()
-        val name = cursor.getString(0)
-        cursor.close()
-        return name
+        val file = File(filesDir, "gtfs_files/routes.txt")
+        val mapReader = CsvMapReader(FileReader(file), CsvPreference.STANDARD_PREFERENCE)
+        val header = mapReader.getHeader(true)
+
+        var row: Map<String, Any>? = null
+        val processors = Array<CellProcessor?>(header.size, { null })
+        while ({ row = mapReader.read(header, processors); row }() != null) {
+            if ((row!!["route_id"] as String) == lineId.id) {
+                mapReader.close()
+                return row!!["route_short_name"] as String
+            }
+        }
+        mapReader.close()
+        throw IllegalArgumentException("Route $lineId not in store")
     }
 
     fun getStopDepartures(stopId: AgencyAndId): Map<AgencyAndId, List<Departure>> {
@@ -171,51 +250,82 @@     }
 
     private fun getStopDeparturesByPlate(plate: Plate): Plate {
         val resultPlate = Plate(Plate.ID(plate.id), HashMap())
-        val cursor = store.rawQuery("select departure_time, trip_headsign, route_id, " +
-                "service_id, wheelchair_accessible, stop_sequence, trip_id, direction_id, shape_id from stop_times join trips using trip_id " +
-                "where stop_id = ? and route_id = ? and trip_headsign = ?",
-                arrayOf(plate.id.stop.toString(), plate.id.line.toString(), plate.id.headsign)) // fixme headsign toLower // test if needed
-        while (cursor.moveToNext()) {
-            val cal = JCalendar.getInstance()
-            val (h, m, s) = cursor.getString(0).split(":")
-            cal.set(JCalendar.HOUR_OF_DAY, h.toInt())
-            cal.set(JCalendar.MINUTE, m.toInt())
-            cal.set(JCalendar.SECOND, s.toInt())
-            val time = cal.secondsAfterMidnight()
-            val serviceId = AgencyAndId(cursor.getString(3))
-            val mode = calendarToMode(serviceId)
-            val lowFloor = cursor.getInt(4) == 1
-            val mod = explainModification(Trip(AgencyAndId(cursor.getString(2)),
-                    serviceId, createTripId(cursor.getString(6)),
-                    cursor.getString(1), cursor.getInt(7),
-                    AgencyAndId(cursor.getString(8))), cursor.getInt(5))
+        val trips = HashMap<String, Map<String, Any>>()
+        val stopTimesFile = File(filesDir, "gtfs_files/stop_times.txt")
+        var mapReader = CsvMapReader(FileReader(stopTimesFile), CsvPreference.STANDARD_PREFERENCE)
+        var header = mapReader.getHeader(true)
 
-            val dep = Departure(plate.id.line, mode, time, lowFloor, mod, plate.id.headsign)
-            if (resultPlate.departures!![serviceId] == null)
-                resultPlate.departures[serviceId] = HashSet()
-            resultPlate.departures[serviceId]!!.add(dep)
+        var stopTimesRow: Map<String, Any>? = null
+        var processors = Array<CellProcessor?>(header.size, { null })
+        while ({ stopTimesRow = mapReader.read(header, processors); stopTimesRow }() != null) {
+            if ((stopTimesRow!!["stop_id"] as String) == plate.id.stop.id) {
+                val tripId = stopTimesRow!!["trip_id"] as String
+                trips[tripId] = stopTimesRow!!
+            }
         }
-        cursor.close()
+        mapReader.close()
+
+        val tripsFile = File(filesDir, "gtfs_files/trips.txt")
+        mapReader = CsvMapReader(FileReader(tripsFile), CsvPreference.STANDARD_PREFERENCE)
+        header = mapReader.getHeader(true)
+
+        var tripsRow: Map<String, Any>? = null
+        processors = Array(header.size, { null })
+        while ({ tripsRow = mapReader.read(header, processors); tripsRow }() != null) {
+            val tripId = tripsRow!!["trip_id"] as String
+            if (tripId in trips && tripsRow!!["route_id"] as String == plate.id.line.id
+                    && tripsRow!!["trip_headsign"] as String == plate.id.headsign) { //check if toLower is needed
+                val cal = JCalendar.getInstance()
+                val (h, m, s) = (trips[tripId]!!["departure_time"] as String).split(":")
+                cal.set(JCalendar.HOUR_OF_DAY, h.toInt())
+                cal.set(JCalendar.MINUTE, m.toInt())
+                cal.set(JCalendar.SECOND, s.toInt())
+                val time = cal.secondsAfterMidnight()
+                val serviceId = AgencyAndId(tripsRow!!["service_id"] as String)
+                val mode = calendarToMode(serviceId)
+                val lowFloor = trips[tripId]!!["wheelchair_accessible"] as String == "1"
+                val mod = explainModification(Trip(AgencyAndId(tripsRow!!["route_id"] as String),
+                        serviceId, createTripId(tripsRow!!["trip_id"] as String),
+                        tripsRow!!["trip_headsign"] as String, Integer.parseInt(tripsRow!!["direction_id"] as String),
+                        AgencyAndId(tripsRow!!["shape_id"] as String)), Integer.parseInt(trips[tripId]!!["stop_sequence"] as String))
+
+                val dep = Departure(plate.id.line, mode, time, lowFloor, mod, plate.id.headsign)
+                if (resultPlate.departures!![serviceId] == null)
+                    resultPlate.departures[serviceId] = HashSet()
+                resultPlate.departures[serviceId]!!.add(dep)
+            }
+        }
+        mapReader.close()
         return resultPlate
     }
 
     fun calendarToMode(serviceId: AgencyAndId): List<Int> {
-        val cursor = store.rawQuery("select monday, tuesday, wednesday, thursday, friday, " +
-                "saturday, sunday from calendar where service_id = ?", arrayOf(serviceId.id))
-        cursor.moveToNext()
-        val calendar = Calendar(cursor.getInt(0), cursor.getInt(1),
-                cursor.getInt(2), cursor.getInt(3), cursor.getInt(4),
-                cursor.getInt(5), cursor.getInt(6))
-        val days = ArrayList<Int>()
-        if (calendar.monday == 1) days.add(0)
-        if (calendar.tuesday == 1) days.add(1)
-        if (calendar.wednesday == 1) days.add(2)
-        if (calendar.thursday == 1) days.add(3)
-        if (calendar.friday == 1) days.add(4)
-        if (calendar.saturday == 1) days.add(5)
-        if (calendar.sunday == 1) days.add(6)
-        cursor.close()
-        return days
+        val file = File(filesDir, "gtfs_files/calendar.txt")
+        val mapReader = CsvMapReader(FileReader(file), CsvPreference.STANDARD_PREFERENCE)
+        val header = mapReader.getHeader(true)
+
+        var row: Map<String, Any>? = null
+        val processors = Array<CellProcessor?>(header.size, { null })
+        while ({ row = mapReader.read(header, processors); row }() != null) {
+            if ((row!!["service_id"] as String) == serviceId.id) {
+                mapReader.close()
+                val calendar = Calendar(row!!["monday"] as String == "1", row!!["tuesday"] as String == "1",
+                        row!!["wednesday"] as String == "1", row!!["thursday"] as String == "1", row!!["friday"] as String == "1",
+                        row!!["saturday"] as String == "1", row!!["sunday"] as String == "1")
+                val days = ArrayList<Int>()
+                if (calendar.monday) days.add(0)
+                if (calendar.tuesday) days.add(1)
+                if (calendar.wednesday) days.add(2)
+                if (calendar.thursday) days.add(3)
+                if (calendar.friday) days.add(4)
+                if (calendar.saturday) days.add(5)
+                if (calendar.sunday) days.add(6)
+
+                return days
+            }
+        }
+        mapReader.close()
+        throw IllegalArgumentException("Service $serviceId not in store")
     }
 
     private fun explainModification(trip: Trip, stopSequence: Int): List<String> {
@@ -235,31 +345,57 @@         return explanations
     }
 
     private fun getRouteForTrip(trip: Trip): Route {
-        val cursor = store.rawQuery("select route_id, agency_id, route_short_name, " +
-                "route_long_name route_desc, route_type, route_color, route_text_color " +
-                "from routes join trips using route_id where trip_id = ?", arrayOf(trip.rawId))
-        cursor.moveToNext()
-        val id = cursor.getString(0)
-        val agency = cursor.getString(1)
-        val shortName = cursor.getString(2)
-        val longName = cursor.getString(3)
-        val desc = cursor.getString(4)
-        val type = cursor.getInt(5)
-        val colour = Integer.parseInt(cursor.getString(6), 16)
-        val textColour = Integer.parseInt(cursor.getString(7), 16)
-        val (to, from) = desc.split("|")
-        val toSplit = to.split("^")
-        val fromSplit = from.split("^")
-        val description = "${toSplit[0]}|${fromSplit[0]}"
-        val modifications = HashMap<String, String>()
-        toSplit.slice(1 until toSplit.size).forEach {
-            val (k, v) = it.split(" - ")
-            modifications[k] = v
+        val routesFile = File(filesDir, "gtfs_files/routes.txt")
+        var mapReader = CsvMapReader(FileReader(routesFile), CsvPreference.STANDARD_PREFERENCE)
+        var header = mapReader.getHeader(true)
+
+        var routeId = ""
+        var row: Map<String, Any>? = null
+        var processors = Array<CellProcessor?>(header.size, { null })
+        while ({ row = mapReader.read(header, processors); row }() != null) {
+            if ((row!!["trip_id"] as String) == trip.rawId) {
+                mapReader.close()
+                routeId = row!!["route_id"] as String
+                break
+            }
+        }
+        if (routeId == "") {
+            mapReader.close()
+            throw IllegalArgumentException("Trip ${trip.rawId} not in store")
         }
-        val route = Route(AgencyAndId(id), AgencyAndId(agency), shortName, longName, description,
-                type, colour, textColour, modifications)
-        cursor.close()
-        return route
+
+        val tripsFile = File(filesDir, "gtfs_files/trips.txt")
+        mapReader = CsvMapReader(FileReader(tripsFile), CsvPreference.STANDARD_PREFERENCE)
+        header = mapReader.getHeader(true)
+
+        var routeRow: Map<String, Any>? = null
+        processors = Array(header.size, { null })
+        while ({ routeRow = mapReader.read(header, processors); routeRow }() != null) {
+            if ((routeRow!!["route_id"] as String) == routeId) {
+                mapReader.close()
+                val id = routeRow!!["route_id"] as String
+                val agency = routeRow!!["agency_id"] as String
+                val shortName = routeRow!!["route_short_name"] as String
+                val longName = routeRow!!["route_long_name"] as String
+                val desc = routeRow!!["route_desc"] as String
+                val type = Integer.parseInt(routeRow!!["route_type"] as String)
+                val colour = Integer.parseInt(routeRow!!["route_color"] as String, 16)
+                val textColour = Integer.parseInt(routeRow!!["route_text_color"] as String, 16)
+                val (to, from) = desc.split("|")
+                val toSplit = to.split("^")
+                val fromSplit = from.split("^")
+                val description = "${toSplit[0]}|${fromSplit[0]}"
+                val modifications = HashMap<String, String>()
+                toSplit.slice(1 until toSplit.size).forEach {
+                    val (k, v) = it.split(" - ")
+                    modifications[k] = v
+                }
+                return Route(AgencyAndId(id), AgencyAndId(agency), shortName, longName, description,
+                        type, colour, textColour, modifications)
+            }
+        }
+        mapReader.close()
+        throw IllegalArgumentException("Trip ${trip.rawId} not in store")
     }
 
 //    fun getLinesForStop(stopId: AgencyAndId): Set<AgencyAndId> {
@@ -269,66 +405,102 @@ //        return lines
 //    }
 
     fun getTripsForStop(stopId: AgencyAndId): Set<Trip> {
+        val tripIds = HashSet<String>()
+        val stopTimesFile = File(filesDir, "gtfs_files/stop_times.txt")
+        var mapReader = CsvMapReader(FileReader(stopTimesFile), CsvPreference.STANDARD_PREFERENCE)
+        var header = mapReader.getHeader(true)
+
+        var stopTimesRow: Map<String, Any>? = null
+        var processors = Array<CellProcessor?>(header.size, { null })
+        while ({ stopTimesRow = mapReader.read(header, processors); stopTimesRow }() != null) {
+            if ((stopTimesRow!!["stop_id"] as String) == stopId.id) {
+                val tripId = stopTimesRow!!["trip_id"] as String
+                tripIds.add(tripId)
+            }
+        }
+        mapReader.close()
+
+
         val trips = HashSet<Trip>()
-        val cursor = store.rawQuery("select route_id, service_id, trip_id, trip_headsign, " +
-                "direction_id, shape_id from stop_times join trips using trip_id " +
-                "where stop_id = ?", arrayOf(stopId.id))
-        while (cursor.moveToNext()) {
-            val routeId = AgencyAndId(cursor.getString(0))
-            val serviceId = AgencyAndId(cursor.getString(1))
-            val tripId = cursor.getString(2)
-            val headsign = cursor.getString(3)
-            val direction = cursor.getInt(4)
-            val shape = AgencyAndId(cursor.getString(5))
-            val trip = Trip(routeId, serviceId, createTripId(tripId), headsign, direction, shape)
-            trips.add(trip)
+        val tripsFile = File(filesDir, "gtfs_files/trips.txt")
+        mapReader = CsvMapReader(FileReader(tripsFile), CsvPreference.STANDARD_PREFERENCE)
+        header = mapReader.getHeader(true)
+
+        var tripsRow: Map<String, Any>? = null
+        processors = Array(header.size, { null })
+        while ({ tripsRow = mapReader.read(header, processors); tripsRow }() != null) {
+            val tripId = tripsRow!!["trip_id"] as String
+            if (tripId in tripIds) {
+                trips.add(Trip(AgencyAndId(tripsRow!!["route_id"] as String),
+                        AgencyAndId(tripsRow!!["service_id"] as String),
+                        createTripId(tripId),
+                        tripsRow!!["trip_headsign"] as String,
+                        Integer.parseInt(tripsRow!!["direction_id"] as String),
+                        AgencyAndId(tripsRow!!["shape_id"] as String)))
+            }
         }
-        cursor.close()
+        mapReader.close()
         return trips
     }
 
     private fun createTripId(rawId: String): Trip.ID {
-        var modification = rawId.split("^")[1]
-        modification = modification.subSequence(0, modification.length - 1) as String
-        val isMain = modification[modification.length - 1] == '+'
-        val modifications = HashSet<Trip.ID.Modification>()
-        modification.split(",").forEach {
-            try {
-                val (id, start, end) = it.split(":")
-                modifications.add(Trip.ID.Modification(AgencyAndId(id), IntRange(start.toInt(), end.toInt())))
-            } catch (e: Exception) {
-                modifications.add(Trip.ID.Modification(AgencyAndId(it), null))
+        if (rawId.contains('^')) {
+            var modification = rawId.split("^")[1]
+            val isMain = modification[modification.length - 1] == '+'
+            if (isMain)
+                modification = modification.subSequence(0, modification.length - 1) as String
+            val modifications = HashSet<Trip.ID.Modification>()
+            modification.split(",").forEach {
+                try {
+                    val (id, start, end) = it.split(":")
+                    modifications.add(Trip.ID.Modification(AgencyAndId(id), IntRange(start.toInt(), end.toInt())))
+                } catch (e: Exception) {
+                    modifications.add(Trip.ID.Modification(AgencyAndId(it), null))
+                }
             }
-        }
-        return Trip.ID(AgencyAndId(rawId.split("^")[0]), modifications, isMain)
+            return Trip.ID(AgencyAndId(rawId.split("^")[0]), modifications, isMain)
+        } else
+            return Trip.ID(AgencyAndId(rawId), HashSet(), false)
     }
 
     fun isEmpty(): Boolean {
-        val cursor = store.rawQuery("select * from feed_info", null)
-        return try {
-            cursor.moveToNext()
-            cursor.close()
-            true
+        try {
+            val file = File(filesDir, "gtfs_files/feed_info.txt")
+            val mapReader = CsvMapReader(FileReader(file), CsvPreference.STANDARD_PREFERENCE)
+            val header = mapReader.getHeader(true)
+
+            val processors = Array<CellProcessor?>(header.size, { null })
+            if (mapReader.read(header, processors) == null) {
+                mapReader.close()
+                return true
+            }
+            mapReader.close()
+            return false
         } catch (e: Exception) {
-            cursor.close()
-            false
+            return true
         }
     }
 
     fun getValidSince(): String {
-        val cursor = store.rawQuery("select feed_start_date from feed_info", null)
-        cursor.moveToNext()
-        val validSince = cursor.getString(0)
-        cursor.close()
-        return validSince
+        val file = File(filesDir, "gtfs_files/feed_info.txt")
+        val mapReader = CsvMapReader(FileReader(file), CsvPreference.STANDARD_PREFERENCE)
+        val header = mapReader.getHeader(true)
+
+        val processors = Array<CellProcessor?>(header.size, { null })
+        val row = mapReader.read(header, processors)
+        mapReader.close()
+        return row["feed_start_date"] as String
     }
 
     fun getValidTill(): String {
-        val cursor = store.rawQuery("select feed_end_date from feed_info", null)
-        cursor.moveToNext()
-        val validTill = cursor.getString(0)
-        cursor.close()
-        return validTill
+        val file = File(filesDir, "gtfs_files/feed_info.txt")
+        val mapReader = CsvMapReader(FileReader(file), CsvPreference.STANDARD_PREFERENCE)
+        val header = mapReader.getHeader(true)
+
+        val processors = Array<CellProcessor?>(header.size, { null })
+        val row = mapReader.read(header, processors)
+        mapReader.close()
+        return row["feed_end_date"] as String
     }
 
     fun getServiceForToday(): AgencyAndId {
@@ -345,28 +517,70 @@     }
 
     private fun getServiceFor(day: Int): AgencyAndId {
         val dow = arrayOf("sunday", "monday", "tuesday", "wednesday", "thursday", "friday", "saturday")
-        val cursor = store.rawQuery("select service_id from calendar where ? = 1", arrayOf(dow[day - 1]))
-        cursor.moveToNext()
-        val service = AgencyAndId(cursor.getString(0))
-        cursor.close()
-        return service
+        val file = File(filesDir, "gtfs_files/calendar.txt")
+        val mapReader = CsvMapReader(FileReader(file), CsvPreference.STANDARD_PREFERENCE)
+        val header = mapReader.getHeader(true)
+
+        var row: Map<String, Any>? = null
+        val processors = Array<CellProcessor?>(header.size, { null })
+        while ({ row = mapReader.read(header, processors); row }() != null) {
+            if ((row!![dow[day - 1]] as String) == "1") {
+                mapReader.close()
+                return AgencyAndId(row!!["service_id"] as String)
+            }
+        }
+        mapReader.close()
+        throw IllegalArgumentException("Day $day not in calendar")
     }
 
     fun getLineForNumber(number: String): AgencyAndId {
-        val cursor = store.rawQuery("select route_id from routes where route_short_name = ?", arrayOf(number))
-        cursor.moveToNext()
-        val id = AgencyAndId(cursor.getString(0))
-        cursor.close()
-        return id
+        val file = File(filesDir, "gtfs_files/routes.txt")
+        val mapReader = CsvMapReader(FileReader(file), CsvPreference.STANDARD_PREFERENCE)
+        val header = mapReader.getHeader(true)
+
+        var row: Map<String, Any>? = null
+        val processors = Array<CellProcessor?>(header.size, { null })
+        while ({ row = mapReader.read(header, processors); row }() != null) {
+            if ((row!!["route_short_name"] as String) == number) {
+                mapReader.close()
+                return AgencyAndId(row!!["route_id"] as String)
+            }
+        }
+        mapReader.close()
+        throw IllegalArgumentException("Route $number not in store")
     }
 
     fun getPlatesForStop(stop: AgencyAndId): Set<Plate.ID> {
         val plates = HashSet<Plate.ID>()
-        val cursor = store.rawQuery("select trip_headsign, route_id from stop_times join trips using trip_id where stop_id = ?", arrayOf(stop.id))
-        while (cursor.moveToNext()) {
-            plates.add(Plate.ID(AgencyAndId(cursor.getString(1)), stop, cursor.getString(0)))
+        val tripIds = HashSet<String>()
+        val stopTimesFile = File(filesDir, "gtfs_files/stop_times.txt")
+        var mapReader = CsvMapReader(FileReader(stopTimesFile), CsvPreference.STANDARD_PREFERENCE)
+        var header = mapReader.getHeader(true)
+
+        var stopTimesRow: Map<String, Any>? = null
+        var processors = Array<CellProcessor?>(header.size, { null })
+        while ({ stopTimesRow = mapReader.read(header, processors); stopTimesRow }() != null) {
+            if ((stopTimesRow!!["stop_id"] as String) == stop.id) {
+                val tripId = stopTimesRow!!["trip_id"] as String
+                tripIds.add(tripId)
+            }
+        }
+        mapReader.close()
+
+
+        val tripsFile = File(filesDir, "gtfs_files/trips.txt")
+        mapReader = CsvMapReader(FileReader(tripsFile), CsvPreference.STANDARD_PREFERENCE)
+        header = mapReader.getHeader(true)
+
+        var tripsRow: Map<String, Any>? = null
+        processors = Array(header.size, { null })
+        while ({ tripsRow = mapReader.read(header, processors); tripsRow }() != null) {
+            if (tripsRow!!["trip_id"] as String in tripIds) {
+                plates.add(Plate.ID(AgencyAndId(tripsRow!!["route_id"] as String), stop, tripsRow!!["trip_headsign"] as String))
+            }
         }
-        cursor.close()
+        mapReader.close()
+
         return plates
     }
 }




diff --git a/app/src/main/res/layout/activity_nodb.xml b/app/src/main/res/layout/activity_nodb.xml
index 63b8cd16a003e2fccabadb5fbc343f558b5a51ff..e34a2552f1412a7a26afa1e361629cc5e0ab92a6 100644
--- a/app/src/main/res/layout/activity_nodb.xml
+++ b/app/src/main/res/layout/activity_nodb.xml
@@ -28,6 +28,7 @@             android:layout_marginTop="8dp"
             android:text=""
             android:textAlignment="center"
             android:textAppearance="@style/TextAppearance.AppCompat.Headline"
+            android:textColor="@color/colorPrimary"
             app:layout_constraintBottom_toBottomOf="parent"
             app:layout_constraintEnd_toEndOf="parent"
             app:layout_constraintStart_toStartOf="parent"