Bimba.git

commit 81b54e7253df59bdaf9f32d589054d46b9b3e3fa

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

partially working indexes which are too time- and memory-consuming

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


diff --git a/.idea/misc.xml b/.idea/misc.xml
index 635999df1e86791ad3787e455b4524e4d8879b93..ba7052b8197ddf8ba8756022d905d03055c7ad60 100644
--- a/.idea/misc.xml
+++ b/.idea/misc.xml
@@ -24,7 +24,7 @@         
       </value>
     </option>
   </component>
-  <component name="ProjectRootManager" version="2" languageLevel="JDK_1_8" default="true" project-jdk-name="1.8" project-jdk-type="JavaSDK">
+  <component name="ProjectRootManager" version="2" languageLevel="JDK_1_7" default="true" project-jdk-name="1.8" project-jdk-type="JavaSDK">
     <output url="file://$PROJECT_DIR$/build/classes" />
   </component>
   <component name="ProjectType">




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 8d6bf18d6f6a6a0baf481c72f38273e6c6b3a1fb..a324a7e458eeb6e4f35bd27f7055dc45f440af2f 100644
--- a/app/src/main/java/ml/adamsprogs/bimba/activities/DashActivity.kt
+++ b/app/src/main/java/ml/adamsprogs/bimba/activities/DashActivity.kt
@@ -99,7 +99,7 @@         searchView.setOnFocusChangeListener(object : FloatingSearchView.OnFocusChangeListener {
             override fun onFocus() {
                 favouritesList.visibility = View.GONE
                 thread {
-                    val newStops = suggestions!!.filter { deAccent(it.name).contains(deAccent(searchView.query), true) }
+                    val newStops = suggestions!!.filter { deAccent(it.name).contains(deAccent(searchView.query), true) } //todo sorted by similarity
                     runOnUiThread { searchView.swapSuggestions(newStops) }
                 }
             }
@@ -113,7 +113,7 @@         searchView.setOnQueryChangeListener({ oldQuery, newQuery ->
             if (oldQuery != "" && newQuery == "")
                 searchView.clearSuggestions()
             thread {
-                val newStops = suggestions!!.filter { deAccent(it.name).contains(deAccent(newQuery), true) }
+                val newStops = suggestions!!.filter { deAccent(it.name).contains(deAccent(newQuery), true) } //todo sorted by similarity
                 runOnUiThread { searchView.swapSuggestions(newStops) }
             }
         })




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 a1d314e65b9c8b3aec0b30abaffbbb7ca2796729..330f14d8b2a941a6bf8af290bba2a4b4f2d52ffe 100644
--- a/app/src/main/java/ml/adamsprogs/bimba/datasources/TimetableDownloader.kt
+++ b/app/src/main/java/ml/adamsprogs/bimba/datasources/TimetableDownloader.kt
@@ -94,39 +94,12 @@             target.deleteRecursively()
             target.mkdir()
             ZipArchive.unzip(gtfs.path, target.path, "")
 
-//            val stopTimesFile = File(filesDir, "gtfs_files/stop_times.txt")
-
-//            val reader = CsvListReader(FileReader(stopTimesFile), CsvPreference.STANDARD_PREFERENCE)
-//            val header = reader.getHeader(true)
-//
-//            val headers = HashMap<String, Boolean>()
-//            val mapReader = CsvListReader(FileReader(stopTimesFile), CsvPreference.STANDARD_PREFERENCE)
-
             val string = getString(R.string.timetable_converting)
             notify(0, string, 1_030_000)
 
             println(Calendar.getInstance().timeInMillis)
 
-//            var row: List<Any>? = null
-//            while ({ row = mapReader.read(); row }() != null) {
-//                val stopId = row!![3] as String
-//                val outFile = File(filesDir, "gtfs_files/stop_times_$stopId.txt")
-//                val writer = CsvWriter(CsvWriterSettings())
-//                if (headers[stopId] == null) {
-//                    val h = writer.writeHeadersToString(header.asList())
-//                    outFile.appendText("$h\r\n")
-//                    headers[stopId] = true
-//                }
-//                if (mapReader.rowNumber % 10_300 == 0)
-//                    notify(mapReader.rowNumber, string, 1_030_000)
-//                val line = writer.writeRowToString(row!!)
-//                outFile.appendText("$line\r\n")
-//            }
-//            mapReader.close()
-//
             gtfs.delete()
-//
-//            stopTimesFile.delete()
 
             createIndices()
             Timetable.getTimetable(this).refresh()




diff --git a/app/src/main/java/ml/adamsprogs/bimba/extensions.kt b/app/src/main/java/ml/adamsprogs/bimba/extensions.kt
index fd9647d5bafa2ac07fb024e19b99edf1e2714329..67673a586af83fae70adb6197996172676bc7dea 100644
--- a/app/src/main/java/ml/adamsprogs/bimba/extensions.kt
+++ b/app/src/main/java/ml/adamsprogs/bimba/extensions.kt
@@ -77,5 +77,6 @@ }
 
 internal fun Context.getSecondaryExternalFilesDir(): File {
     val dirs = this.getExternalFilesDirs(null)
-    return dirs[dirs.size - 1]
+    return dirs[0]
+//    return dirs[dirs.size - 1]
 }
\ No newline at end of file




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 c639958a8867b8f10ee1891a9b4e871f2fc6d9c2..a374294a2e5013f22d61dafad0ec497b685080d4 100644
--- a/app/src/main/java/ml/adamsprogs/bimba/models/Timetable.kt
+++ b/app/src/main/java/ml/adamsprogs/bimba/models/Timetable.kt
@@ -25,7 +25,7 @@ import kotlin.collections.HashMap
 import kotlin.collections.HashSet
 import java.util.Calendar as JCalendar
 
-class Timetable private constructor() { //todo indices
+class Timetable private constructor() { //fixme uses much too much RAM
     companion object {
         private var timetable: Timetable? = null
 
@@ -34,7 +34,18 @@             return if (timetable == null || force)
                 if (context != null) {
                     timetable = Timetable()
                     timetable!!.filesDir = context.getSecondaryExternalFilesDir()
-                    //timetable!!.cacheManager = CacheManager.getCacheManager(context)
+                    val gtfsDir = File(timetable!!.filesDir, "gtfs_dir")
+                    timetable!!.agencyFile = File(gtfsDir, "agency.txt")
+                    timetable!!.calendarFile = File(gtfsDir, "calendar.txt")
+                    timetable!!.calendarDatesFile = File(gtfsDir, "calendar_dates.txt")
+                    timetable!!.feedInfoFile = File(gtfsDir, "feed_info.txt")
+                    timetable!!.routesFile = File(gtfsDir, "routes.txt")
+                    timetable!!.shapesFile = File(gtfsDir, "shapes.txt")
+                    timetable!!.stopsFile = File(gtfsDir, "stops.txt")
+                    timetable!!.stopTimesFile = File(gtfsDir, "stop_times.txt")
+                    timetable!!.tripsFile = File(gtfsDir, "trips.txt")
+                    timetable!!.stopsIndexFile = File(gtfsDir, "stop_index.txt")
+                    timetable!!.tripsIndexFile = File(gtfsDir, "trip_index.txt")
                     timetable!!
                 } else
                     throw IllegalArgumentException("new timetable requested and no context given")
@@ -43,15 +54,22 @@                 timetable!!
         }
     }
 
-    //private lateinit var cacheManager: CacheManager
+    private lateinit var agencyFile: File
+    private lateinit var calendarFile: File
+    private lateinit var calendarDatesFile: File
+    private lateinit var feedInfoFile: File
+    private lateinit var routesFile: File
+    private lateinit var shapesFile: File
+    private lateinit var stopsFile: File
+    private lateinit var stopTimesFile: File
+    private lateinit var tripsFile: File
+    private lateinit var stopsIndexFile: File
+    private lateinit var tripsIndexFile: File
     private var _stops: List<StopSuggestion>? = null
     private lateinit var filesDir: File
     private val tripsCache = HashMap<String, Array<String>>()
 
     fun refresh() {
-        //cacheManager.recreate(getStopDeparturesByPlates(cacheManager.keys().toSet()))
-
-        //getStopSuggestions(true)
     }
 
     fun getStopSuggestions(context: Context, force: Boolean = false): List<StopSuggestion> {
@@ -61,6 +79,8 @@
 
         val settings = CsvParserSettings()
         settings.format.setLineSeparator("\r\n")
+        settings.format.quote = '"'
+        settings.isHeaderExtractionEnabled = true
         val parser = CsvParser(settings)
 
         val ids = HashMap<String, HashSet<AgencyAndId>>()
@@ -114,59 +134,19 @@     fun getHeadlinesForStop(stops: Set): Map>> { //fixme adds one (not-)random shed
         val trips = HashMap<String, HashSet<String>>()
         val routes = HashMap<String, Pair<String, String>>()
         val headsigns = HashMap<AgencyAndId, Pair<String, HashSet<String>>>()
-        val settings = CsvParserSettings()
-        settings.format.setLineSeparator("\r\n")
-        settings.format.quote = '"'
-        settings.isHeaderExtractionEnabled = true
-        val parser = CsvParser(settings)
 
-        val stopIndex = HashMap<String, List<Long>>()
-
-        var timeStart = JCalendar.getInstance().timeInMillis
-        var timeEnd = JCalendar.getInstance().timeInMillis
-        println(JCalendar.getInstance().timeInMillis)
-
-        val reader = File(filesDir, "gtfs_files/stop_index.yml").bufferedReader() //fixme 5s
-        val json = Gson().fromJson(reader.readText(), JsonObject::class.java)
-        reader.close()
-
-        timeEnd = JCalendar.getInstance().timeInMillis
-        println("${timeEnd-timeStart}: ${JCalendar.getInstance().timeInMillis}")
-        timeStart = JCalendar.getInstance().timeInMillis
-        json.entrySet().forEach {//fixme 3s
-            stopIndex[it.key] = ArrayList()
-            it.value.asJsonArray.mapTo(stopIndex[it.key] as ArrayList) { it.asLong }
-        }
-
-        timeEnd = JCalendar.getInstance().timeInMillis
-        println("${timeEnd-timeStart}: ${JCalendar.getInstance().timeInMillis}")
-        timeStart = JCalendar.getInstance().timeInMillis
         val stopIds = stops.map { it.id }
 
-        val lines = stopIndex.filter { it.key in stopIds }.flatMap { it.value }.sorted()
-        timeEnd = JCalendar.getInstance().timeInMillis
-        println("${timeEnd-timeStart}: ${JCalendar.getInstance().timeInMillis}")
-        timeStart = JCalendar.getInstance().timeInMillis
-        val pickupPossibilities = arrayOf("0", "3")
-        parser.beginParsing(File(filesDir, "gtfs_files/stop_times.txt"))
-        var lineKey = 0
-        lines.forEach {//fixme 3s
-//            println("At line ${parser.context.currentLine()}, skipping ${lines[lineKey] - parser.context.currentLine() - 1} lines to line ${lines[lineKey]}")
-            parser.context.skipLines(lines[lineKey] - parser.context.currentLine() - 1)
-            val line = parser.parseNext()
-            val stopId = line[3]
+        parseStopTimesWithStopIndex(stopIds) {
+            val stopId = it[3]
 //            println("Parsing line ${parser.context.currentLine()}; stopId: $stopId")
-            if (line[6] in pickupPossibilities) {
+            if (it[6] != "1") {
                 if (trips[stopId] == null)
                     trips[stopId] = HashSet()
-                trips[stopId]!!.add(line[0])
+                trips[stopId]!!.add(it[0])
             }
-            lineKey++
         }
 
-        timeEnd = JCalendar.getInstance().timeInMillis
-        println("${timeEnd-timeStart}: ${JCalendar.getInstance().timeInMillis}")
-        timeStart = JCalendar.getInstance().timeInMillis
         if (tripsCache.isEmpty())
             createTripCache()
         tripsCache.forEach {
@@ -174,9 +154,6 @@             routes[it.key] = Pair(it.value[0], it.value[3])
         }
 
 
-        timeEnd = JCalendar.getInstance().timeInMillis
-        println("${timeEnd-timeStart}: ${JCalendar.getInstance().timeInMillis}")
-        timeStart = JCalendar.getInstance().timeInMillis
         trips.forEach {
             val headsign = HashSet<String>()
             it.value.forEach {
@@ -185,8 +162,6 @@             }
             headsigns[AgencyAndId(it.key)] = Pair(getStopCode(AgencyAndId(it.key)), headsign)
         }
 
-        timeEnd = JCalendar.getInstance().timeInMillis
-        println("${timeEnd-timeStart}: ${JCalendar.getInstance().timeInMillis}")
         return headsigns
         /*
         1435 -> (AWF03, {232 → Os. Rusa})
@@ -199,10 +174,54 @@         4586 -> (AWF73, {10 → Franowo, 29 → Franowo, 6 → Miłostowo, 5 → Stomil, 18 → Franowo, 15 → Franowo, 12 → Starołęka, 74 → Os. Orła Białego})
         */
     }
 
+    private fun parseStopTimesWithStopIndex(stopIds: List<String>, process: (Array<String>) -> Unit) {
+        val lines = readIndex(stopIds, File(filesDir, "gtfs_files/stop_index.yml"))
+        parseStopTimesWithIndex(lines, process)
+    }
+
+    private fun parseStopTimesWithTripIndex(tripIds: List<String>, process: (Array<String>) -> Unit) {
+        val lines = readIndex(tripIds, File(filesDir, "gtfs_files/trip_index.yml"))
+        parseStopTimesWithIndex(lines, process)
+    }
+
+    private fun readIndex(ids: List<String>, indexFile: File): List<Long> {
+        val index = HashMap<String, List<Long>>()
+
+        val reader = indexFile.bufferedReader() //fixme 5s
+        val json = Gson().fromJson(reader.readText(), JsonObject::class.java)
+        reader.close()
+
+        json.entrySet().forEach {
+            //fixme 3s
+            index[it.key] = ArrayList()
+            it.value.asJsonArray.mapTo(index[it.key] as ArrayList) { it.asLong }
+        }
+        return index.filter { it.key in ids }.flatMap { it.value }.sorted()
+    }
+
+    private fun parseStopTimesWithIndex(lines: List<Long>, process: (Array<String>) -> Unit) {
+        val settings = CsvParserSettings()
+        settings.format.setLineSeparator("\r\n")
+        settings.format.quote = '"'
+        settings.isHeaderExtractionEnabled = true
+        val parser = CsvParser(settings)
+
+        parser.beginParsing(File(filesDir, "gtfs_files/stop_times.txt"))
+        lines.forEach {
+            //fixme 3s
+//            println("At line ${parser.context.currentLine()}, skipping ${lines[lineKey] - parser.context.currentLine() - 1} lines to line ${lines[lineKey]}")
+            parser.context.skipLines(it - parser.context.currentLine() - 1)
+            val line = parser.parseNext()
+            process(line)
+        }
+
+    }
+
     private fun createTripCache() {
         val settings = CsvParserSettings()
         settings.format.setLineSeparator("\r\n")
         settings.format.quote = '"'
+        settings.isHeaderExtractionEnabled = true
         val parser = CsvParser(settings)
         val stopsFile = File(filesDir, "gtfs_files/trips.txt")
         parser.parseAll(stopsFile).forEach {
@@ -263,7 +282,11 @@     }
 
     fun getStopDepartures(stopId: AgencyAndId): Map<AgencyAndId, List<Departure>> {
         println("getStopDepartures: ${JCalendar.getInstance().timeInMillis}")
-        val trips = getTripsForStop(stopId)
+//        val trips = getTripsForStop(stopId)
+        val trips = HashMap<String, Trip>()
+        tripsCache.forEach {
+            trips[it.key] = tripFromCache(it.key)
+        }
         val segment = StopSegment(stopId, null)
         segment.fillPlates()
         return getStopDeparturesBySegment(segment, trips)
@@ -274,26 +297,26 @@
     private fun getStopDeparturesBySegment(segment: StopSegment, trips: Map<String, Trip>): HashMap<AgencyAndId, List<Departure>> {
         println("getStopDeparturesBySegment: ${JCalendar.getInstance().timeInMillis}")
         val departures = HashMap<AgencyAndId, ArrayList<Departure>>()
-        val settings = CsvParserSettings()
-        settings.format.quote = '"'
-        settings.format.setLineSeparator("\r\n")
-        settings.isHeaderExtractionEnabled = true
-        var file = File(filesDir, "gtfs_files/stop_times_${segment.stop.id}.txt")
-        val parser = CsvParser(settings)
+
         val tripsInStop = HashMap<String, Pair<Int, Int>>()
-        parser.parseAll(file).forEach {
+
+        parseStopTimesWithStopIndex(listOf(segment.stop.id)) {
             tripsInStop[it[0]] = Pair(parseTime(it[2]), it[4].toInt())
         }
-        println("getStopDeparturesBySegment: ${JCalendar.getInstance().timeInMillis}")
 
-        file = File(filesDir, "gtfs_files/calendar.txt")
+        val file = File(filesDir, "gtfs_files/calendar.txt")
+        val settings = CsvParserSettings()
+        settings.format.setLineSeparator("\r\n")
+        settings.format.quote = '"'
+        settings.isHeaderExtractionEnabled = true
+        val parser = CsvParser(settings)
         parser.parseAll(file).forEach {
             departures[AgencyAndId(it[0])] = ArrayList()
         }
 
         tripsInStop.forEach {
             //fixme this part is long --- cache is the only option
-            departures[trips[it.key]!!.serviceId]!!.add(Departure(trips[it.key]!!.routeId,
+            departures[trips[it.key]!!.serviceId]!!.add(Departure(trips[it.key]!!.routeId, // fixme null?
                     calendarToMode(trips[it.key]!!.serviceId),
                     it.value.first, trips[it.key]!!.wheelchairAccessible,
                     explainModification(trips[it.key]!!, it.value.second),
@@ -309,42 +332,6 @@         println(": ${JCalendar.getInstance().timeInMillis}")
         return sortedDepartures
     }
 
-//    private fun getStopDeparturesByPlate(plate: Plate, trips: Map<String, Trip>): Plate { //fixme<c:optimisation> takes too long --- cache
-//        println("getStopDeparturesByPlate: ${JCalendar.getInstance().timeInMillis}")
-//        val resultPlate = Plate(Plate.ID(plate.id), HashMap())
-//        val stopTimes = HashMap<String, Map<String, Any>>()
-//        val stopTimesFile = File(filesDir, "gtfs_files/stop_times_${plate.id.stop.id}.txt")
-//        val mapReader = CsvMapReader(FileReader(stopTimesFile), CsvPreference.STANDARD_PREFERENCE)
-//        val header = mapReader.getHeader(true)
-//
-//        var stopTimesRow: Map<String, Any>? = null
-//        val processors = Array<CellProcessor?>(header.size, { null })
-//        while ({ stopTimesRow = mapReader.read(header, processors); stopTimesRow }() != null) {
-//            val tripId = stopTimesRow!!["trip_id"] as String
-//            stopTimes[tripId] = stopTimesRow!!
-//        }
-//        mapReader.close()
-//
-//        trips.forEach {
-//            if (it.value.routeId == plate.id.line &&
-//                    it.value.headsign == plate.id.headsign) {
-//                val time = parseTime(stopTimes[it.key]!!["departure_time"] as String)
-//                val serviceId = it.value.serviceId
-//                val mode = calendarToMode(serviceId)
-//                val lowFloor = it.value.wheelchairAccessible
-//                val mod = explainModification(it.value,
-//                        Integer.parseInt(stopTimes[it.key]!!["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)
-//            }
-//        }
-//        println("</>: ${JCalendar.getInstance().timeInMillis}")
-//        return resultPlate
-//    }
-
     private fun parseTime(time: String): Int {
         val cal = JCalendar.getInstance()
         val (h, m, s) = time.split(":")
@@ -455,26 +442,13 @@             return Route(AgencyAndId(id), AgencyAndId(agency), shortName, longName, description,
                     type, colour, textColour, modifications)
         }
     }
-
-//    fun getLinesForStop(stopId: AgencyAndId): Set<AgencyAndId> {
-//        val lines = HashSet<AgencyAndId>()
-//        store.allStopTimes.filter { it.stop.id == stopId }.forEach { lines.add(it.trip.route.id) }
-//        return lines
-//    }
 
     fun getTripsForStop(stopId: AgencyAndId): HashMap<String, Trip> {
         val tripIds = HashSet<String>()
-        val stopTimesFile = File(filesDir, "gtfs_files/stop_times_${stopId.id}.txt")
-        val mapReader = CsvMapReader(FileReader(stopTimesFile), CsvPreference.STANDARD_PREFERENCE)
-        val header = mapReader.getHeader(true)
 
-        var stopTimesRow: Map<String, Any>? = null
-        val processors = Array<CellProcessor?>(header.size, { null })
-        while ({ stopTimesRow = mapReader.read(header, processors); stopTimesRow }() != null) {
-            val tripId = stopTimesRow!!["trip_id"] as String
-            tripIds.add(tripId)
+        parseStopTimesWithStopIndex(listOf(stopId.id)) {
+            tripIds.add(it[0])
         }
-        mapReader.close()
 
         val filteredTrips = HashMap<String, Trip>()
 
@@ -599,17 +573,10 @@     }
 
     fun getPlatesForStop(stop: AgencyAndId): Set<Plate.ID> {
         val tripIds = HashSet<String>()
-        val stopTimesFile = File(filesDir, "gtfs_files/stop_times_${stop.id}.txt")
-        val mapReader = CsvMapReader(FileReader(stopTimesFile), CsvPreference.STANDARD_PREFERENCE)
-        val header = mapReader.getHeader(true)
 
-        var stopTimesRow: Map<String, Any>? = null
-        val processors = Array<CellProcessor?>(header.size, { null })
-        while ({ stopTimesRow = mapReader.read(header, processors); stopTimesRow }() != null) {
-            val tripId = stopTimesRow!!["trip_id"] as String
-            tripIds.add(tripId)
+        parseStopTimesWithStopIndex(listOf(stop.id)) {
+            tripIds.add(it[0])
         }
-        mapReader.close()
 
         val filteredPlates = HashSet<Plate.ID>()
         tripIds.forEach {
@@ -620,12 +587,16 @@         return filteredPlates
     }
 
     fun getTripGraphs(id: AgencyAndId): List<Map<Int, List<Int>>> {
+        val tripsToDo = HashSet<String>()
         if (tripsCache.isEmpty())
             createTripCache()
         tripsCache.forEach {
             if (it.value[0] == id.id) {
-                //todo needs stop_times.txt indexed by trips
+                tripsToDo.add(it.key) //todo and direction {0,1}
             }
+        }
+        parseStopTimesWithTripIndex(tripsToDo.toList()) {
+            //todo create graph
         }
         val map = ArrayList<HashMap<Int, List<Int>>>()
         val map0 = HashMap<Int, List<Int>>()