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>>()