From 102c02840724fb99560ce076d808714dbf1048e1 Mon Sep 17 00:00:00 2001 From: Cilly Leang Date: Tue, 5 May 2026 03:23:11 +1000 Subject: [PATCH] refactor: optimisation around stoptimes - moved stoptime related functionality into new core:data:stoptime module - will feature all the different realtime stoptime sources to be integrated later - create proper database schema for future migrations - deduplicate trips into stoppingpatterns, since many trips share the exact same stopping pattern - stoptimes are now linked to stoppingpatterns instead - stoppingpattern ids are generated from a hash composed of all stoptimes - stoptimes now use deltas for arrival time to save space --- core/data/build.gradle.kts | 1 + .../banksia/core/data/ClientDataDiModule.kt | 8 +- .../repositories/ClientStopTimeRepository.kt | 16 ---- .../data/sources/trip/TripRemoteDataSource.kt | 18 ---- .../data/repositories/StopTimeRepository.kt | 7 -- core/data/stoptime/build.gradle.kts | 60 +++++++++++++ .../core/data/StopTimeDataDiModule.client.kt | 11 +++ .../repositories/StopTimeRepository.client.kt | 19 ++++ .../stoptime/StopTimeRemoteDataSource.kt | 18 +--- .../banksia/core/data/StopTimeDataDiModule.kt | 13 +++ .../data/repositories/StopTimeRepository.kt | 15 ++++ .../stoptime/StopTimeLocalDataSource.kt | 22 ++--- .../core/data/StopTimeDataDiModule.jvm.kt | 9 ++ .../repositories/StopTimeRepository.jvm.kt | 13 +++ core/sqld/build.gradle.kts | 1 + .../core/sqld/DatabaseManager.android.kt | 4 +- .../lava/banksia/core/sqld/DatabaseManager.kt | 4 +- .../lava/banksia/core/sqld/SqldDiModule.kt | 1 + .../banksia/core/sqld/mappers/StopTime.kt | 18 ++-- .../core/sqld/mappers/StoppingPattern.kt | 22 +++++ .../lava/banksia/core/sqld/mappers/Trip.kt | 27 +++--- .../moe/lava/banksia/core/sqld/Stop.sq | 10 +-- .../moe/lava/banksia/core/sqld/StopTime.sq | 11 +-- .../lava/banksia/core/sqld/StoppingPattern.sq | 10 +++ .../moe/lava/banksia/core/sqld/Trip.sq | 12 ++- .../src/commonMain/sqldelight/schema/1.db | Bin 0 -> 81920 bytes .../banksia/core/sqld/DatabaseManager.ios.kt | 5 +- .../banksia/core/sqld/DatabaseManager.jvm.kt | 4 +- .../moe/lava/banksia/core/model/StopTime.kt | 39 ++++++-- .../lava/banksia/core/model/StopTimeDated.kt | 26 ------ .../banksia/core/model/StoppingPattern.kt | 16 ++++ .../moe/lava/banksia/core/model/Trip.kt | 14 +-- .../lava/banksia/server/gtfs/GtfsParser.kt | 84 ++++++++++++++---- .../server/gtfs/structures/GtfsStopTime.kt | 2 +- .../moe/lava/banksia/server/Application.kt | 2 +- .../moe/lava/banksia/server/GtfsImporter.kt | 24 ++--- server/src/main/resources/logback.xml | 2 +- settings.gradle.kts | 1 + .../ui/screens/map/MapScreenViewModel.kt | 50 ++++++----- 39 files changed, 396 insertions(+), 223 deletions(-) delete mode 100644 core/data/client/src/commonMain/kotlin/moe/lava/banksia/core/data/repositories/ClientStopTimeRepository.kt delete mode 100644 core/data/client/src/commonMain/kotlin/moe/lava/banksia/core/data/sources/trip/TripRemoteDataSource.kt delete mode 100644 core/data/src/commonMain/kotlin/moe/lava/banksia/core/data/repositories/StopTimeRepository.kt create mode 100644 core/data/stoptime/build.gradle.kts create mode 100644 core/data/stoptime/src/clientMain/kotlin/moe/lava/banksia/core/data/StopTimeDataDiModule.client.kt create mode 100644 core/data/stoptime/src/clientMain/kotlin/moe/lava/banksia/core/data/repositories/StopTimeRepository.client.kt rename core/data/{client/src/commonMain => stoptime/src/clientMain}/kotlin/moe/lava/banksia/core/data/sources/stoptime/StopTimeRemoteDataSource.kt (54%) create mode 100644 core/data/stoptime/src/commonMain/kotlin/moe/lava/banksia/core/data/StopTimeDataDiModule.kt create mode 100644 core/data/stoptime/src/commonMain/kotlin/moe/lava/banksia/core/data/repositories/StopTimeRepository.kt rename core/data/{client => stoptime}/src/commonMain/kotlin/moe/lava/banksia/core/data/sources/stoptime/StopTimeLocalDataSource.kt (58%) create mode 100644 core/data/stoptime/src/jvmMain/kotlin/moe/lava/banksia/core/data/StopTimeDataDiModule.jvm.kt create mode 100644 core/data/stoptime/src/jvmMain/kotlin/moe/lava/banksia/core/data/repositories/StopTimeRepository.jvm.kt create mode 100644 core/sqld/src/commonMain/kotlin/moe/lava/banksia/core/sqld/mappers/StoppingPattern.kt create mode 100644 core/sqld/src/commonMain/sqldelight/moe/lava/banksia/core/sqld/StoppingPattern.sq create mode 100644 core/sqld/src/commonMain/sqldelight/schema/1.db delete mode 100644 core/src/commonMain/kotlin/moe/lava/banksia/core/model/StopTimeDated.kt create mode 100644 core/src/commonMain/kotlin/moe/lava/banksia/core/model/StoppingPattern.kt diff --git a/core/data/build.gradle.kts b/core/data/build.gradle.kts index 6f78b5c..8c89aff 100644 --- a/core/data/build.gradle.kts +++ b/core/data/build.gradle.kts @@ -27,6 +27,7 @@ kotlin { sourceSets { commonMain.dependencies { implementation(projects.core) + api(projects.core.data.stoptime) } } } diff --git a/core/data/client/src/commonMain/kotlin/moe/lava/banksia/core/data/ClientDataDiModule.kt b/core/data/client/src/commonMain/kotlin/moe/lava/banksia/core/data/ClientDataDiModule.kt index 2cbfcaa..0384f88 100644 --- a/core/data/client/src/commonMain/kotlin/moe/lava/banksia/core/data/ClientDataDiModule.kt +++ b/core/data/client/src/commonMain/kotlin/moe/lava/banksia/core/data/ClientDataDiModule.kt @@ -10,16 +10,12 @@ import kotlinx.serialization.json.Json import moe.lava.banksia.core.Constants import moe.lava.banksia.core.data.repositories.ClientRouteRepository import moe.lava.banksia.core.data.repositories.ClientStopRepository -import moe.lava.banksia.core.data.repositories.ClientStopTimeRepository import moe.lava.banksia.core.data.repositories.RouteRepository import moe.lava.banksia.core.data.repositories.StopRepository -import moe.lava.banksia.core.data.repositories.StopTimeRepository import moe.lava.banksia.core.data.sources.route.RouteLocalDataSource import moe.lava.banksia.core.data.sources.route.RouteRemoteDataSource import moe.lava.banksia.core.data.sources.stop.StopLocalDataSource import moe.lava.banksia.core.data.sources.stop.StopRemoteDataSource -import moe.lava.banksia.core.data.sources.stoptime.StopTimeLocalDataSource -import moe.lava.banksia.core.data.sources.stoptime.StopTimeRemoteDataSource import moe.lava.banksia.core.sqld.sqldDiModule import moe.lava.banksia.core.util.log import moe.lava.banksia.data.ptv.PtvService @@ -29,6 +25,7 @@ import org.koin.dsl.module val clientDataDiModule = module { includes(sqldDiModule) + includes(stopTimeDataDiModule) // HTTP Clients singleOf(::PtvService) @@ -56,11 +53,8 @@ val clientDataDiModule = module { singleOf(::RouteRemoteDataSource) singleOf(::StopLocalDataSource) singleOf(::StopRemoteDataSource) - singleOf(::StopTimeLocalDataSource) - singleOf(::StopTimeRemoteDataSource) // Repositories singleOf(::ClientRouteRepository) bind RouteRepository::class singleOf(::ClientStopRepository) bind StopRepository::class - singleOf(::ClientStopTimeRepository) bind StopTimeRepository::class } diff --git a/core/data/client/src/commonMain/kotlin/moe/lava/banksia/core/data/repositories/ClientStopTimeRepository.kt b/core/data/client/src/commonMain/kotlin/moe/lava/banksia/core/data/repositories/ClientStopTimeRepository.kt deleted file mode 100644 index aea3159..0000000 --- a/core/data/client/src/commonMain/kotlin/moe/lava/banksia/core/data/repositories/ClientStopTimeRepository.kt +++ /dev/null @@ -1,16 +0,0 @@ -package moe.lava.banksia.core.data.repositories - -import moe.lava.banksia.core.data.sources.stoptime.StopTimeLocalDataSource -import moe.lava.banksia.core.data.sources.stoptime.StopTimeRemoteDataSource -import moe.lava.banksia.core.model.StopTimeDated - -internal class ClientStopTimeRepository internal constructor( - private val local: StopTimeLocalDataSource, - private val remote: StopTimeRemoteDataSource, -) : StopTimeRepository { - override suspend fun getForStop(id: String): List { - return local - .getAtStop(id) - .ifEmpty { remote.getAtStop(id) } - } -} diff --git a/core/data/client/src/commonMain/kotlin/moe/lava/banksia/core/data/sources/trip/TripRemoteDataSource.kt b/core/data/client/src/commonMain/kotlin/moe/lava/banksia/core/data/sources/trip/TripRemoteDataSource.kt deleted file mode 100644 index d1067d8..0000000 --- a/core/data/client/src/commonMain/kotlin/moe/lava/banksia/core/data/sources/trip/TripRemoteDataSource.kt +++ /dev/null @@ -1,18 +0,0 @@ -package moe.lava.banksia.core.data.sources.trip - -import io.ktor.client.HttpClient -import kotlinx.datetime.DayOfWeek -import kotlinx.datetime.TimeZone -import kotlinx.datetime.todayIn -import moe.lava.banksia.core.model.Trip -import kotlin.time.Clock - -internal class TripRemoteDataSource( - private val client: HttpClient, -) { - suspend fun get( - day: DayOfWeek? = Clock.System.todayIn(TimeZone.currentSystemDefault()).dayOfWeek, - ): List { - return listOf() - } -} diff --git a/core/data/src/commonMain/kotlin/moe/lava/banksia/core/data/repositories/StopTimeRepository.kt b/core/data/src/commonMain/kotlin/moe/lava/banksia/core/data/repositories/StopTimeRepository.kt deleted file mode 100644 index 87d01ac..0000000 --- a/core/data/src/commonMain/kotlin/moe/lava/banksia/core/data/repositories/StopTimeRepository.kt +++ /dev/null @@ -1,7 +0,0 @@ -package moe.lava.banksia.core.data.repositories - -import moe.lava.banksia.core.model.StopTimeDated - -interface StopTimeRepository { - suspend fun getForStop(id: String): List -} diff --git a/core/data/stoptime/build.gradle.kts b/core/data/stoptime/build.gradle.kts new file mode 100644 index 0000000..086e749 --- /dev/null +++ b/core/data/stoptime/build.gradle.kts @@ -0,0 +1,60 @@ +import org.jetbrains.kotlin.gradle.dsl.JvmTarget + +plugins { + alias(libs.plugins.kotlinMultiplatform) + alias(libs.plugins.kotlinSerialization) + alias(libs.plugins.androidMultiplatformLibrary) + alias(libs.plugins.ksp) +} + +kotlin { + android { + namespace = "moe.lava.banksia.core.data.stoptime" + compileSdk = libs.versions.android.compileSdk.get().toInt() + + compilerOptions { + jvmTarget.set(JvmTarget.JVM_11) + } + } + + compilerOptions { + freeCompilerArgs.add("-opt-in=kotlin.time.ExperimentalTime") + freeCompilerArgs.add("-Xexpect-actual-classes") + } + + iosArm64() + iosSimulatorArm64() + + jvm() + + sourceSets { + val clientMain by creating { + dependsOn(commonMain.get()) + } + + androidMain.get().dependsOn(clientMain) + iosArm64Main.get().dependsOn(clientMain) + iosSimulatorArm64Main.get().dependsOn(clientMain) + + androidMain.dependencies { + implementation(libs.ktor.client.okhttp) + } + commonMain.dependencies { + implementation(libs.okio) + implementation(libs.koin.core) + implementation(libs.ktor.client.core) + implementation(libs.ktor.client.contentnegotiation) + implementation(libs.ktor.serialization.kotlinx.json) + implementation(libs.kotlinx.coroutines.core) + implementation(libs.kotlinx.datetime) + implementation(libs.kotlinx.serialization.json) + implementation(libs.kotlinx.serialization.protobuf) + + implementation(projects.core) + implementation(projects.core.sqld) + } + iosMain.dependencies { + implementation(libs.ktor.client.darwin) + } + } +} diff --git a/core/data/stoptime/src/clientMain/kotlin/moe/lava/banksia/core/data/StopTimeDataDiModule.client.kt b/core/data/stoptime/src/clientMain/kotlin/moe/lava/banksia/core/data/StopTimeDataDiModule.client.kt new file mode 100644 index 0000000..2f83304 --- /dev/null +++ b/core/data/stoptime/src/clientMain/kotlin/moe/lava/banksia/core/data/StopTimeDataDiModule.client.kt @@ -0,0 +1,11 @@ +package moe.lava.banksia.core.data + +import moe.lava.banksia.core.data.repositories.StopTimeRepository +import moe.lava.banksia.core.data.sources.stoptime.StopTimeRemoteDataSource +import org.koin.core.module.dsl.singleOf +import org.koin.dsl.module + +internal actual val platformModule = module { + singleOf(::StopTimeRepository) + singleOf(::StopTimeRemoteDataSource) +} diff --git a/core/data/stoptime/src/clientMain/kotlin/moe/lava/banksia/core/data/repositories/StopTimeRepository.client.kt b/core/data/stoptime/src/clientMain/kotlin/moe/lava/banksia/core/data/repositories/StopTimeRepository.client.kt new file mode 100644 index 0000000..ecaff8e --- /dev/null +++ b/core/data/stoptime/src/clientMain/kotlin/moe/lava/banksia/core/data/repositories/StopTimeRepository.client.kt @@ -0,0 +1,19 @@ +package moe.lava.banksia.core.data.repositories + +import kotlinx.coroutines.flow.flow +import kotlinx.datetime.LocalDate +import moe.lava.banksia.core.data.sources.stoptime.StopTimeLocalDataSource +import moe.lava.banksia.core.data.sources.stoptime.StopTimeRemoteDataSource + +actual class StopTimeRepository internal constructor( + private val local: StopTimeLocalDataSource, + private val remote: StopTimeRemoteDataSource, +) { + actual suspend fun getForStop(id: String, date: LocalDate) = flow { + emit(local.getAtStop(id, date)) + + remote.getAtStop(id, date) + .takeIf { it.isNotEmpty() } + ?.let { emit(it) } + } +} diff --git a/core/data/client/src/commonMain/kotlin/moe/lava/banksia/core/data/sources/stoptime/StopTimeRemoteDataSource.kt b/core/data/stoptime/src/clientMain/kotlin/moe/lava/banksia/core/data/sources/stoptime/StopTimeRemoteDataSource.kt similarity index 54% rename from core/data/client/src/commonMain/kotlin/moe/lava/banksia/core/data/sources/stoptime/StopTimeRemoteDataSource.kt rename to core/data/stoptime/src/clientMain/kotlin/moe/lava/banksia/core/data/sources/stoptime/StopTimeRemoteDataSource.kt index 0633a18..1d338ce 100644 --- a/core/data/client/src/commonMain/kotlin/moe/lava/banksia/core/data/sources/stoptime/StopTimeRemoteDataSource.kt +++ b/core/data/stoptime/src/clientMain/kotlin/moe/lava/banksia/core/data/sources/stoptime/StopTimeRemoteDataSource.kt @@ -7,7 +7,7 @@ import io.ktor.client.request.parameter import kotlinx.datetime.LocalDate import kotlinx.datetime.TimeZone import kotlinx.datetime.todayIn -import moe.lava.banksia.core.model.StopTimeDated +import moe.lava.banksia.core.model.StopTime import kotlin.time.Clock internal class StopTimeRemoteDataSource( @@ -16,21 +16,9 @@ internal class StopTimeRemoteDataSource( suspend fun getAtStop( stopId: String, date: LocalDate? = Clock.System.todayIn(TimeZone.currentSystemDefault()), - ): List { + ): List { return client.get("stoptimes/by_stop/${stopId}") { parameter("date", date) - }.body>() + }.body>() } - - /*suspend fun get( - stop: String? = null, - trip: String? = null, - day: DayOfWeek? = Clock.System.todayIn(TimeZone.currentSystemDefault()).dayOfWeek, - ): List { - return client.get("stoptimes") { - stop?.let { parameter("stop", it) } - trip?.let { parameter("trip", it) } - day?.let { parameter("day", it) } - }.body>() - }*/ } diff --git a/core/data/stoptime/src/commonMain/kotlin/moe/lava/banksia/core/data/StopTimeDataDiModule.kt b/core/data/stoptime/src/commonMain/kotlin/moe/lava/banksia/core/data/StopTimeDataDiModule.kt new file mode 100644 index 0000000..d46affa --- /dev/null +++ b/core/data/stoptime/src/commonMain/kotlin/moe/lava/banksia/core/data/StopTimeDataDiModule.kt @@ -0,0 +1,13 @@ +package moe.lava.banksia.core.data + +import moe.lava.banksia.core.data.sources.stoptime.StopTimeLocalDataSource +import org.koin.core.module.Module +import org.koin.core.module.dsl.singleOf +import org.koin.dsl.module + +internal expect val platformModule: Module; + +val stopTimeDataDiModule = module { + includes(platformModule) + singleOf(::StopTimeLocalDataSource) +} diff --git a/core/data/stoptime/src/commonMain/kotlin/moe/lava/banksia/core/data/repositories/StopTimeRepository.kt b/core/data/stoptime/src/commonMain/kotlin/moe/lava/banksia/core/data/repositories/StopTimeRepository.kt new file mode 100644 index 0000000..2de0c10 --- /dev/null +++ b/core/data/stoptime/src/commonMain/kotlin/moe/lava/banksia/core/data/repositories/StopTimeRepository.kt @@ -0,0 +1,15 @@ +package moe.lava.banksia.core.data.repositories + +import kotlinx.coroutines.flow.Flow +import kotlinx.datetime.LocalDate +import kotlinx.datetime.TimeZone +import kotlinx.datetime.todayIn +import moe.lava.banksia.core.model.StopTime +import kotlin.time.Clock + +expect class StopTimeRepository { + suspend fun getForStop( + id: String, + date: LocalDate = Clock.System.todayIn(TimeZone.currentSystemDefault()), + ): Flow> +} diff --git a/core/data/client/src/commonMain/kotlin/moe/lava/banksia/core/data/sources/stoptime/StopTimeLocalDataSource.kt b/core/data/stoptime/src/commonMain/kotlin/moe/lava/banksia/core/data/sources/stoptime/StopTimeLocalDataSource.kt similarity index 58% rename from core/data/client/src/commonMain/kotlin/moe/lava/banksia/core/data/sources/stoptime/StopTimeLocalDataSource.kt rename to core/data/stoptime/src/commonMain/kotlin/moe/lava/banksia/core/data/sources/stoptime/StopTimeLocalDataSource.kt index 78ca64b..03ebbda 100644 --- a/core/data/client/src/commonMain/kotlin/moe/lava/banksia/core/data/sources/stoptime/StopTimeLocalDataSource.kt +++ b/core/data/stoptime/src/commonMain/kotlin/moe/lava/banksia/core/data/sources/stoptime/StopTimeLocalDataSource.kt @@ -4,23 +4,19 @@ import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.IO import kotlinx.coroutines.withContext import kotlinx.datetime.LocalDate -import kotlinx.datetime.TimeZone -import kotlinx.datetime.todayIn -import moe.lava.banksia.core.model.StopTimeDated +import moe.lava.banksia.core.model.StopTime import moe.lava.banksia.core.model.atDate import moe.lava.banksia.core.sqld.StopTimeQueries import moe.lava.banksia.core.sqld.mappers.asModel import moe.lava.banksia.core.util.serialise -import kotlin.time.Clock +import org.koin.core.component.KoinComponent +import org.koin.core.component.get -internal class StopTimeLocalDataSource( - private val queries: StopTimeQueries, -) { - suspend fun getAtStop( - stopId: String, - date: LocalDate = Clock.System.todayIn(TimeZone.currentSystemDefault()), - ): List { - return withContext(Dispatchers.IO) { +internal class StopTimeLocalDataSource : KoinComponent { + private val queries get() = get() + + suspend fun getAtStop(stopId: String, date: LocalDate): List { + return withContext(context = Dispatchers.IO) { queries .getForStopDated( listOf(date.dayOfWeek).serialise().toLong(), @@ -29,7 +25,7 @@ internal class StopTimeLocalDataSource( ) .executeAsList() .map { it.asModel().atDate(date) } - .sortedBy { it.departureTime } + .sortedBy { it.time.departure } } } } diff --git a/core/data/stoptime/src/jvmMain/kotlin/moe/lava/banksia/core/data/StopTimeDataDiModule.jvm.kt b/core/data/stoptime/src/jvmMain/kotlin/moe/lava/banksia/core/data/StopTimeDataDiModule.jvm.kt new file mode 100644 index 0000000..70ef406 --- /dev/null +++ b/core/data/stoptime/src/jvmMain/kotlin/moe/lava/banksia/core/data/StopTimeDataDiModule.jvm.kt @@ -0,0 +1,9 @@ +package moe.lava.banksia.core.data + +import moe.lava.banksia.core.data.repositories.StopTimeRepository +import org.koin.core.module.dsl.singleOf +import org.koin.dsl.module + +internal actual val platformModule = module { + singleOf(::StopTimeRepository) +} diff --git a/core/data/stoptime/src/jvmMain/kotlin/moe/lava/banksia/core/data/repositories/StopTimeRepository.jvm.kt b/core/data/stoptime/src/jvmMain/kotlin/moe/lava/banksia/core/data/repositories/StopTimeRepository.jvm.kt new file mode 100644 index 0000000..b4c37a6 --- /dev/null +++ b/core/data/stoptime/src/jvmMain/kotlin/moe/lava/banksia/core/data/repositories/StopTimeRepository.jvm.kt @@ -0,0 +1,13 @@ +package moe.lava.banksia.core.data.repositories + +import kotlinx.coroutines.flow.flow +import kotlinx.datetime.LocalDate +import moe.lava.banksia.core.data.sources.stoptime.StopTimeLocalDataSource + +actual class StopTimeRepository internal constructor( + private val local: StopTimeLocalDataSource, +) { + actual suspend fun getForStop(id: String, date: LocalDate) = flow { + emit(local.getAtStop(id, date)) + } +} diff --git a/core/sqld/build.gradle.kts b/core/sqld/build.gradle.kts index d86247a..472a908 100644 --- a/core/sqld/build.gradle.kts +++ b/core/sqld/build.gradle.kts @@ -47,6 +47,7 @@ sqldelight { databases { register("BanksiaDatabase") { packageName.set("moe.lava.banksia.core.sqld") + schemaOutputDirectory.set(file("src/commonMain/sqldelight/schema")) } } } diff --git a/core/sqld/src/androidMain/kotlin/moe/lava/banksia/core/sqld/DatabaseManager.android.kt b/core/sqld/src/androidMain/kotlin/moe/lava/banksia/core/sqld/DatabaseManager.android.kt index b9f2247..c47613c 100644 --- a/core/sqld/src/androidMain/kotlin/moe/lava/banksia/core/sqld/DatabaseManager.android.kt +++ b/core/sqld/src/androidMain/kotlin/moe/lava/banksia/core/sqld/DatabaseManager.android.kt @@ -5,10 +5,10 @@ import app.cash.sqldelight.driver.android.AndroidSqliteDriver import org.koin.core.component.KoinComponent import org.koin.core.component.get -actual class DatabaseManager actual constructor() : KoinComponent { +actual class DatabaseManager : KoinComponent { actual val database by lazy { val ctx = get().applicationContext - val driver = AndroidSqliteDriver(BanksiaDatabase.Schema, ctx, "timetable.db") + val driver = AndroidSqliteDriver(BanksiaDatabase.Schema, ctx, "${DBNAME}.db") BanksiaDatabase(driver) } } diff --git a/core/sqld/src/commonMain/kotlin/moe/lava/banksia/core/sqld/DatabaseManager.kt b/core/sqld/src/commonMain/kotlin/moe/lava/banksia/core/sqld/DatabaseManager.kt index c6b29f1..983eb58 100644 --- a/core/sqld/src/commonMain/kotlin/moe/lava/banksia/core/sqld/DatabaseManager.kt +++ b/core/sqld/src/commonMain/kotlin/moe/lava/banksia/core/sqld/DatabaseManager.kt @@ -1,7 +1,7 @@ package moe.lava.banksia.core.sqld -import org.koin.core.component.KoinComponent +internal const val DBNAME = "timetable" -expect class DatabaseManager() : KoinComponent { +expect class DatabaseManager() { val database: BanksiaDatabase } diff --git a/core/sqld/src/commonMain/kotlin/moe/lava/banksia/core/sqld/SqldDiModule.kt b/core/sqld/src/commonMain/kotlin/moe/lava/banksia/core/sqld/SqldDiModule.kt index 24ab9bd..deee453 100644 --- a/core/sqld/src/commonMain/kotlin/moe/lava/banksia/core/sqld/SqldDiModule.kt +++ b/core/sqld/src/commonMain/kotlin/moe/lava/banksia/core/sqld/SqldDiModule.kt @@ -11,6 +11,7 @@ val sqldDiModule = module { factory { get().serviceExceptionQueries } factory { get().shapeQueries } factory { get().stopQueries } + factory { get().stoppingPatternQueries } factory { get().stopTimeQueries } factory { get().tripQueries } } diff --git a/core/sqld/src/commonMain/kotlin/moe/lava/banksia/core/sqld/mappers/StopTime.kt b/core/sqld/src/commonMain/kotlin/moe/lava/banksia/core/sqld/mappers/StopTime.kt index c8b1c44..26d5390 100644 --- a/core/sqld/src/commonMain/kotlin/moe/lava/banksia/core/sqld/mappers/StopTime.kt +++ b/core/sqld/src/commonMain/kotlin/moe/lava/banksia/core/sqld/mappers/StopTime.kt @@ -3,23 +3,25 @@ package moe.lava.banksia.core.sqld.mappers import moe.lava.banksia.core.model.FutureTime import moe.lava.banksia.core.model.FutureTime.Companion.asInt import moe.lava.banksia.core.model.StopTime +import moe.lava.banksia.core.model.TimeType import moe.lava.banksia.core.sqld.StopTime as DbStopTime fun DbStopTime.asModel() = StopTime( - tripId = tripId, + patternId = patternId, stopId = stopId, - arrivalTime = FutureTime.fromInt(arrivalTime.toInt()), - departureTime = FutureTime.fromInt(departureTime.toInt()), - headsign = null, + time = TimeType.Undated( + arrival = FutureTime.fromInt((departureTime + arrivalDelta).toInt()), + departure = FutureTime.fromInt(departureTime.toInt()), + ), pickupType = pickupType.toInt(), dropOffType = dropOffType.toInt(), ) -fun StopTime.asDb() = DbStopTime( - tripId = tripId, +fun StopTime.Undated.asDb() = DbStopTime( + patternId = patternId, stopId = stopId, - arrivalTime = arrivalTime.asInt().toLong(), - departureTime = departureTime.asInt().toLong(), + arrivalDelta = (time.arrival.asInt() - time.departure.asInt()).toLong(), + departureTime = time.departure.asInt().toLong(), pickupType = pickupType.toLong(), dropOffType = dropOffType.toLong(), ) diff --git a/core/sqld/src/commonMain/kotlin/moe/lava/banksia/core/sqld/mappers/StoppingPattern.kt b/core/sqld/src/commonMain/kotlin/moe/lava/banksia/core/sqld/mappers/StoppingPattern.kt new file mode 100644 index 0000000..e50aa85 --- /dev/null +++ b/core/sqld/src/commonMain/kotlin/moe/lava/banksia/core/sqld/mappers/StoppingPattern.kt @@ -0,0 +1,22 @@ +package moe.lava.banksia.core.sqld.mappers + +import moe.lava.banksia.core.model.StopTime +import moe.lava.banksia.core.model.StoppingPattern +import moe.lava.banksia.core.sqld.StoppingPattern as DbStoppingPattern + +fun DbStoppingPattern.asModel(stoptimes: List) = StoppingPattern.Undated( + id = id, + routeId = routeId, + shapeId = shapeId, + headsign = headsign, + wheelchairAccessible = wheelchairAccessible == 1L, + stoptimes = stoptimes, +) + +fun StoppingPattern.Undated.asDb() = DbStoppingPattern( + id = id, + routeId = routeId, + shapeId = shapeId, + headsign = headsign, + wheelchairAccessible = if (wheelchairAccessible) 1L else 0L, +) diff --git a/core/sqld/src/commonMain/kotlin/moe/lava/banksia/core/sqld/mappers/Trip.kt b/core/sqld/src/commonMain/kotlin/moe/lava/banksia/core/sqld/mappers/Trip.kt index 36cdad5..b3443fb 100644 --- a/core/sqld/src/commonMain/kotlin/moe/lava/banksia/core/sqld/mappers/Trip.kt +++ b/core/sqld/src/commonMain/kotlin/moe/lava/banksia/core/sqld/mappers/Trip.kt @@ -1,32 +1,27 @@ package moe.lava.banksia.core.sqld.mappers import moe.lava.banksia.core.model.Service +import moe.lava.banksia.core.model.StoppingPattern import moe.lava.banksia.core.model.Trip import moe.lava.banksia.core.sqld.Trip as DbTrip -fun DbTrip.asModel(service: Service): Trip { +fun DbTrip.asModel(pattern: StoppingPattern.Undated, service: Service): Trip.Undated { if (serviceId != service.id) { throw IllegalArgumentException("trip and service id mismatch (${serviceId} != ${service.id})") } return Trip( - id = id, - routeId = routeId, + id = gtfsId, + pattern = pattern, service = service, - shapeId = shapeId, - tripHeadsign = tripHeadsign, - directionId = directionId, - blockId = blockId, - wheelchairAccessible = wheelchairAccessible == 1L + directionId = directionId.toInt(), + blockId = blockId.toString(), ) } -fun Trip.asDb() = DbTrip( - id = id, - routeId = routeId, +fun Trip.Undated.asDb() = DbTrip( + gtfsId = id, + patternId = pattern.id, serviceId = service.id, - shapeId = shapeId, - tripHeadsign = tripHeadsign, - directionId = directionId, - blockId = blockId, - wheelchairAccessible = if (wheelchairAccessible) 1L else 0L + directionId = directionId.toLong(), + blockId = blockId?.toLong(), ) diff --git a/core/sqld/src/commonMain/sqldelight/moe/lava/banksia/core/sqld/Stop.sq b/core/sqld/src/commonMain/sqldelight/moe/lava/banksia/core/sqld/Stop.sq index 3ac06b9..4af5c50 100644 --- a/core/sqld/src/commonMain/sqldelight/moe/lava/banksia/core/sqld/Stop.sq +++ b/core/sqld/src/commonMain/sqldelight/moe/lava/banksia/core/sqld/Stop.sq @@ -15,7 +15,7 @@ getAll: SELECT * FROM Stop; getAllParentless: -SELECT * FROM Stop WHERE platformCode IS NULL AND parent IS NULL; +SELECT * FROM Stop WHERE platformCode IS NOT NULL AND parent IS NULL; get: SELECT * FROM Stop WHERE id == ?; @@ -32,8 +32,8 @@ UPDATE Stop SET parent = ? WHERE id IN ?; getByRoute: SELECT Stop.* FROM Stop INNER JOIN StopTime ON StopTime.stopId == Stop.id -INNER JOIN Trip ON Trip.id == StopTime.tripId -WHERE Trip.routeId == :id +INNER JOIN StoppingPattern ON StoppingPattern.id == StopTime.patternId +WHERE StoppingPattern.routeId == :id GROUP BY Stop.id; -- I vibecoded this, sorry @@ -41,8 +41,8 @@ getParentsByRoute: WITH RECURSIVE Tree AS ( SELECT Stop.* FROM Stop INNER JOIN StopTime ON StopTime.stopId == Stop.id - INNER JOIN Trip ON Trip.id == StopTime.tripId - WHERE Trip.routeId == :id + INNER JOIN StoppingPattern ON StoppingPattern.id == StopTime.patternId + WHERE StoppingPattern.routeId == :id GROUP BY Stop.id UNION ALL diff --git a/core/sqld/src/commonMain/sqldelight/moe/lava/banksia/core/sqld/StopTime.sq b/core/sqld/src/commonMain/sqldelight/moe/lava/banksia/core/sqld/StopTime.sq index f2dc824..45b3f10 100644 --- a/core/sqld/src/commonMain/sqldelight/moe/lava/banksia/core/sqld/StopTime.sq +++ b/core/sqld/src/commonMain/sqldelight/moe/lava/banksia/core/sqld/StopTime.sq @@ -1,11 +1,11 @@ CREATE TABLE StopTime ( - tripId TEXT NOT NULL REFERENCES Trip (id), + patternId INTEGER NOT NULL REFERENCES StoppingPattern (id), stopId TEXT NOT NULL REFERENCES Stop (id), - arrivalTime INTEGER NOT NULL, + arrivalDelta INTEGER NOT NULL, departureTime INTEGER NOT NULL, pickupType INTEGER NOT NULL, dropOffType INTEGER NOT NULL, - PRIMARY KEY (tripId, stopId) + PRIMARY KEY (patternId, stopId) ) WITHOUT ROWID; CREATE INDEX idx_StopTime_stopId ON StopTime (stopId); @@ -16,8 +16,9 @@ INSERT OR REPLACE INTO StopTime VALUES ?; getForStopDated: SELECT DISTINCT StopTime.* FROM StopTime INNER JOIN Service ON Service.days & :days = :days AND :date BETWEEN Service.start AND Service.`end` -INNER JOIN Trip ON Trip.serviceId == Service.id LEFT JOIN ServiceException ON ServiceException.serviceId == Service.id AND ServiceException.date == :date -WHERE StopTime.tripId == Trip.id +INNER JOIN Trip ON Trip.serviceId == Service.id +INNER JOIN StoppingPattern ON StoppingPattern.id == Trip.patternId +WHERE StopTime.patternId == StoppingPattern.id AND StopTime.stopId IN (SELECT Stop.id FROM Stop WHERE Stop.parent == :stopId OR Stop.id == :stopId) AND ServiceException.type IS NULL; diff --git a/core/sqld/src/commonMain/sqldelight/moe/lava/banksia/core/sqld/StoppingPattern.sq b/core/sqld/src/commonMain/sqldelight/moe/lava/banksia/core/sqld/StoppingPattern.sq new file mode 100644 index 0000000..cc1c5ab --- /dev/null +++ b/core/sqld/src/commonMain/sqldelight/moe/lava/banksia/core/sqld/StoppingPattern.sq @@ -0,0 +1,10 @@ +CREATE TABLE StoppingPattern ( + id INTEGER PRIMARY KEY NOT NULL, + routeId TEXT NOT NULL REFERENCES Route (id), + shapeId TEXT NOT NULL REFERENCES Shape (id), + headsign TEXT NOT NULL, + wheelchairAccessible INTEGER NOT NULL +); + +insert: +INSERT OR REPLACE INTO StoppingPattern VALUES ?; diff --git a/core/sqld/src/commonMain/sqldelight/moe/lava/banksia/core/sqld/Trip.sq b/core/sqld/src/commonMain/sqldelight/moe/lava/banksia/core/sqld/Trip.sq index 0fd3c1f..c53b62a 100644 --- a/core/sqld/src/commonMain/sqldelight/moe/lava/banksia/core/sqld/Trip.sq +++ b/core/sqld/src/commonMain/sqldelight/moe/lava/banksia/core/sqld/Trip.sq @@ -1,14 +1,12 @@ CREATE TABLE Trip ( - id TEXT PRIMARY KEY NOT NULL, - routeId TEXT NOT NULL REFERENCES Route (id), + gtfsId TEXT PRIMARY KEY NOT NULL, + patternId INTEGER NOT NULL REFERENCES StoppingPattern (id), serviceId TEXT NOT NULL REFERENCES Service (id), - shapeId TEXT NOT NULL REFERENCES Shape (id), - tripHeadsign TEXT NOT NULL, - directionId TEXT NOT NULL, - blockId TEXT, - wheelchairAccessible INTEGER NOT NULL + blockId INTEGER, + directionId INTEGER NOT NULL ); +CREATE INDEX idx_Trip_patternId ON Trip (patternId); CREATE INDEX idx_Trip_serviceId ON Trip (serviceId); insert: diff --git a/core/sqld/src/commonMain/sqldelight/schema/1.db b/core/sqld/src/commonMain/sqldelight/schema/1.db new file mode 100644 index 0000000000000000000000000000000000000000..feaacb3260f779a72f844ab265f00a2d091b9bb1 GIT binary patch literal 81920 zcmWFz^vNtqRY=P(%1ta$FlG>7U}R))P*7lCVBlq7U=U$I01%%A!DV1XV&h`+GU)aG z=H>svAk1dRz;D5SmM@W?lQ)V_f#*1{KKCS^d0cv&>|CtuZ`fzBd$GM?o62T~%cxQD z(GVC7fzc2c4S~TE0-?fe;)bG(#hH02sTG+i74acOnFaC1sYPX($*Ge ztAeMWi)(}erWyr*KLwCF1r4}1%}^n1#ug-&l%y8rL5vl~ZY*34!dSSFW{e=4xUno_ zIo!3uCHVy*nYpR)#U=R#o+&V{5Qf<>8K^>#;V>Zu4Txq<9|1OTZBfQ_6w~7i5{pvv zN2105efR1EN7Qh@VYdSDG;!VHzY*<5LnVi-RF-0ZbDi5@1DOBO!bR4UjI) zCJ8=vacynJ=Jb-pq?}ZcqY5(f(gPsjipJ%K8Xn^4J5aj9W7!;}C?HZ}z=O3cr7wY4q16EX&Us{stnW7Nl8W94QQV4Q&a}9F!b9N0@ z2m&ck(8x^DglH(vNG!msAsD0pNkc|zVoGsldLF735W~tdQd4u1GZHh49Fvn%i;FXp za#G=rhB=f=v$2|oU0hU@u{jbPG@t|t!n`Pf01|=vJiR2X80IJ3UWX?=3>%UCj}f+r zkbp!V%p#a`Q8Yu8AcaO!PJVK>Cp=~#!H|+!l$u>N z$0#9Dl3#!n42eZWnPrJNE~z;si5S5T4V2V^#G;bYqEwI{aHuKBOwKMX2&pWjkT3%k5a3dR12xn@;zS2qL1IaUf|HNG6FhvlG&MQc#BIfKHS0A)mI84l;Op}G`JHPq4YDiR!QaCbq%uLMsbO)SBc zevy+6q6*eg0GX!ArD@N`CLXMd)5B=aj5kF07JH*AGrPF7G-Hz@axg%Zv0(Ew)USlo zHKTpxDEpoOekIg z3lZ%hP?+OzbY5w0Qfd)6+99!xnW?!nHJR|GPOw|z4Z##M0|NsGs2vIF)H4WxSfh9} z1V%$(Gz3ONU^E0qLtr!nMnhmU1V%$(Gz3ONU^E0qLtwau02jz(qy2wSD2(FK5Eu=C z(GVC7fzc2c4S~@R7!85Z5Eu=C(GVC7fzc2cwjnS&|37S_cGP2|Aut*OqaiRF0;3@? z8UmvsFd71*Aut*OqaiRF0;3@S4}sD7e|S)gGDbsSGz3ONU^E0qLtr!nMnhmU1V%$( zGz3ONU^E0qLtxm3!07z{u#MVLkBx@FXb6mkz-S1JhQMeDjE2By2#kinXb6mkz-S1J zh5$SSM(6+GK{3i04S~@R7!85Z5Eu=C(GVC7fzc2c4S~@R7!85Z5Eu=CVH*OY^Z&y( zYDYab8UmvsFd71*Aut*OqaiRF0;3@?8UmvsFd71*Aut*O@DLcC|Az;~C}T7PMnhmU z1V%$(Gz3ONU^E0qLtr!nMnhmU1V%$(Gz5li2#n7E58J36_1I_#jE2By2#kinXb6mk zz-S1JhQMeDjE2By2#kinXb8YVV08W;9u%XD(GVC7fzc2c4S~@R7!85Z5Eu=C(GVC7 zfzc2c4S~@R7`7oW+W#N6Q9J6f(GVC7fzc2c4S~@R7!85Z5Eu=C(GVC7fzc2c4S~@R zfQP{7`hR#(j50<;U^E0qLtr!nMnhmU1V%$(Gz3ONU^E0qLtr!nMnhoOhQR3j|FDhP zQICy=z-S1JhQMeDjE2By2#kinXb6mkz-S1JhQMeDjD`R_1V-oo;XyIV7!85Z5Eu=C z(GVC7fzc2c4S~@R7!85Z5Eu=C(GVC7fngg0qx1j6Hfl#bHW~t>Aut*OqaiRF0;3@? z8UmvsFd71*Aut*OqaiRF0&p( + val patternId: Long, val stopId: String, - val arrivalTime: FutureTime, - val departureTime: FutureTime, - val headsign: String?, + val time: T, val pickupType: Int, val dropOffType: Int, +) { + typealias Dated = StopTime + typealias Undated = StopTime +} + +@Serializable +sealed class TimeType { + @Serializable + data class Undated( + val arrival: FutureTime, + val departure: FutureTime, + ) : TimeType() + + @Serializable + data class Dated( + val arrival: LocalDateTime, + val departure: LocalDateTime, + ) : TimeType() +} + +fun StopTime.atDate(date: LocalDate) = StopTime( + patternId = patternId, + stopId = stopId, + time = TimeType.Dated( + arrival = time.arrival.atDate(date), + departure = time.departure.atDate(date), + ), + pickupType = pickupType, + dropOffType = dropOffType, ) diff --git a/core/src/commonMain/kotlin/moe/lava/banksia/core/model/StopTimeDated.kt b/core/src/commonMain/kotlin/moe/lava/banksia/core/model/StopTimeDated.kt deleted file mode 100644 index 1bd75c6..0000000 --- a/core/src/commonMain/kotlin/moe/lava/banksia/core/model/StopTimeDated.kt +++ /dev/null @@ -1,26 +0,0 @@ -package moe.lava.banksia.core.model - -import kotlinx.datetime.LocalDate -import kotlinx.datetime.LocalDateTime -import kotlinx.serialization.Serializable - -@Serializable -data class StopTimeDated( - val tripId: String, - val stopId: String, - val arrivalTime: LocalDateTime, - val departureTime: LocalDateTime, - val headsign: String?, - val pickupType: Int, - val dropOffType: Int, -) - -fun StopTime.atDate(date: LocalDate) = StopTimeDated( - tripId = tripId, - stopId = stopId, - arrivalTime = arrivalTime.atDate(date), - departureTime = departureTime.atDate(date), - headsign = headsign, - pickupType = pickupType, - dropOffType = dropOffType, -) diff --git a/core/src/commonMain/kotlin/moe/lava/banksia/core/model/StoppingPattern.kt b/core/src/commonMain/kotlin/moe/lava/banksia/core/model/StoppingPattern.kt new file mode 100644 index 0000000..1374cff --- /dev/null +++ b/core/src/commonMain/kotlin/moe/lava/banksia/core/model/StoppingPattern.kt @@ -0,0 +1,16 @@ +package moe.lava.banksia.core.model + +import kotlinx.serialization.Serializable + +@Serializable +data class StoppingPattern( + val id: Long, + val routeId: String, + val shapeId: String, + val headsign: String, + val wheelchairAccessible: Boolean, + val stoptimes: List>, +) { + typealias Dated = StoppingPattern + typealias Undated = StoppingPattern +} diff --git a/core/src/commonMain/kotlin/moe/lava/banksia/core/model/Trip.kt b/core/src/commonMain/kotlin/moe/lava/banksia/core/model/Trip.kt index 6edb538..752d6d2 100644 --- a/core/src/commonMain/kotlin/moe/lava/banksia/core/model/Trip.kt +++ b/core/src/commonMain/kotlin/moe/lava/banksia/core/model/Trip.kt @@ -3,13 +3,13 @@ package moe.lava.banksia.core.model import kotlinx.serialization.Serializable @Serializable -data class Trip( +data class Trip( val id: String, - val routeId: String, + val pattern: StoppingPattern, val service: Service, - val shapeId: String, - val tripHeadsign: String, - val directionId: String, + val directionId: Int, val blockId: String?, - val wheelchairAccessible: Boolean, -) +) { + typealias Dated = Trip + typealias Undated = Trip +} diff --git a/server/gtfs/src/main/kotlin/moe/lava/banksia/server/gtfs/GtfsParser.kt b/server/gtfs/src/main/kotlin/moe/lava/banksia/server/gtfs/GtfsParser.kt index 1a5ec29..c844499 100644 --- a/server/gtfs/src/main/kotlin/moe/lava/banksia/server/gtfs/GtfsParser.kt +++ b/server/gtfs/src/main/kotlin/moe/lava/banksia/server/gtfs/GtfsParser.kt @@ -18,6 +18,7 @@ import kotlinx.serialization.decodeFromString import kotlinx.serialization.modules.EmptySerializersModule import kotlinx.serialization.serializer import moe.lava.banksia.core.Constants +import moe.lava.banksia.core.model.FutureTime.Companion.asInt import moe.lava.banksia.core.model.Route import moe.lava.banksia.core.model.RouteType import moe.lava.banksia.core.model.Service @@ -25,6 +26,8 @@ import moe.lava.banksia.core.model.ServiceException import moe.lava.banksia.core.model.Shape import moe.lava.banksia.core.model.Stop import moe.lava.banksia.core.model.StopTime +import moe.lava.banksia.core.model.StoppingPattern +import moe.lava.banksia.core.model.TimeType import moe.lava.banksia.core.model.Trip import moe.lava.banksia.core.util.Point import moe.lava.banksia.server.gtfs.structures.GtfsRoute @@ -35,6 +38,8 @@ import moe.lava.banksia.server.gtfs.structures.GtfsStop import moe.lava.banksia.server.gtfs.structures.GtfsStopTime import moe.lava.banksia.server.gtfs.structures.GtfsTrip import java.io.File +import java.nio.ByteBuffer +import java.security.MessageDigest import java.util.zip.ZipFile import kotlin.time.ExperimentalTime @@ -46,8 +51,7 @@ sealed class GtfsData { data class ServiceExceptionChunk(val exceptions: List) : GtfsData() data class ShapeChunk(val shapes: List) : GtfsData() data class StopChunk(val stops: List) : GtfsData() - data class StopTimeChunk(val stopTimes: List) : GtfsData() - data class TripChunk(val trips: List) : GtfsData() + data class TripChunk(val trips: List) : GtfsData() } class GtfsParser( @@ -129,7 +133,6 @@ class GtfsParser( .filter { it.name == "trips.txt" } .flatMap { fd -> parseTrips(fd, services) - .also { emit(GtfsData.TripChunk(it)) } } .associateBy { it.id } @@ -137,13 +140,53 @@ class GtfsParser( .filter { it.name == "stop_times.txt" } .forEach { fd -> log.info("parsing stop times for ${fd.parent}...") - parseStopTimes(fd, trips) { seq -> - seq.chunked(1000000) - .forEach { emit(GtfsData.StopTimeChunk(it)) } + parseStopTimes(fd) { seq -> + val times = ArrayList>(1000100) + seq.forEach { pair -> + val (_, stoptime) = pair + if (times.size > 1000000 && stoptime.patternId == 1L) { + emit(GtfsData.TripChunk(processStoptimes(trips, times))) + times.clear() + } + + times.add(pair) + } + emit(GtfsData.TripChunk(processStoptimes(trips, times))) } } } + private fun hashCalc(headsign: String, stops: List): Long { + val inst = MessageDigest.getInstance("SHA-256") + inst.update(headsign.toByteArray()) + stops.forEach { + inst.update(it.stopId.toByteArray()) + val dint = it.time.departure.asInt() + inst.update((dint).toByte()) + inst.update((dint shr 8).toByte()) + val aint = it.time.arrival.asInt() + inst.update((aint).toByte()) + inst.update((aint shr 8).toByte()) + } + + val buf = inst.digest().slice(0..7).toByteArray() + buf[0] = 0 + buf[1] = 0 + return ByteBuffer.wrap(buf).long + } + + private fun processStoptimes(trips: Map, times: ArrayList>) = + times.groupBy { it.first } + .map { (tripId, pairs) -> + val trip = trips[tripId]!! + val stoptimes = pairs.map { it.second } + val hash = hashCalc(trip.pattern.headsign, stoptimes) + trip.copy(pattern = trip.pattern.copy( + id = hash, + stoptimes = stoptimes.map { it.copy(patternId = hash) } + )) + } + private fun parseRoutes(fd: File) = fd.parseCsv() .map { with(it) { @@ -180,16 +223,17 @@ class GtfsParser( ) } } - private inline fun parseStopTimes(fd: File, trips: Map, block: (Sequence) -> Unit) = + private inline fun parseStopTimes(fd: File, block: (Sequence>) -> Unit) = fd.parseCsvSequence { seq -> seq .map { with(it) { - StopTime( - tripId = trip_id, + it.trip_id to StopTime( + patternId = stop_sequence, stopId = stop_id, - arrivalTime = GtfsStopTime.parseGtfsTime(arrival_time), - departureTime = GtfsStopTime.parseGtfsTime(departure_time), - headsign = stop_headsign.ifEmpty { trips[trip_id]!!.tripHeadsign }, + time = TimeType.Undated( + arrival = GtfsStopTime.parseGtfsTime(arrival_time), + departure = GtfsStopTime.parseGtfsTime(departure_time), + ), pickupType = pickup_type, dropOffType = drop_off_type, ) @@ -230,15 +274,19 @@ class GtfsParser( private fun parseTrips(fd: File, services: Map) = fd.parseCsv() .map { with(it) { - Trip( + Trip.Undated( id = trip_id, - routeId = route_id, + pattern = StoppingPattern( + id = 0, + routeId = route_id, + shapeId = shape_id, + headsign = trip_headsign, + wheelchairAccessible = wheelchair_accessible == "1", + stoptimes = listOf() + ), service = services["${fd.parentFile.name}_${service_id}"]!!, - shapeId = shape_id, - tripHeadsign = trip_headsign, - directionId = direction_id, + directionId = direction_id.toInt(), blockId = block_id.ifEmpty { null }, - wheelchairAccessible = wheelchair_accessible == "1", ) } } diff --git a/server/gtfs/src/main/kotlin/moe/lava/banksia/server/gtfs/structures/GtfsStopTime.kt b/server/gtfs/src/main/kotlin/moe/lava/banksia/server/gtfs/structures/GtfsStopTime.kt index 33da78f..c0bbaf2 100644 --- a/server/gtfs/src/main/kotlin/moe/lava/banksia/server/gtfs/structures/GtfsStopTime.kt +++ b/server/gtfs/src/main/kotlin/moe/lava/banksia/server/gtfs/structures/GtfsStopTime.kt @@ -10,7 +10,7 @@ internal data class GtfsStopTime( val arrival_time: String, val departure_time: String, val stop_id: String, - val stop_sequence: Int, + val stop_sequence: Long, val stop_headsign: String, val pickup_type: Int, val drop_off_type: Int, diff --git a/server/src/main/kotlin/moe/lava/banksia/server/Application.kt b/server/src/main/kotlin/moe/lava/banksia/server/Application.kt index 9ffd03a..17a9002 100644 --- a/server/src/main/kotlin/moe/lava/banksia/server/Application.kt +++ b/server/src/main/kotlin/moe/lava/banksia/server/Application.kt @@ -151,7 +151,7 @@ fun Application.module() { ) .executeAsList() .map { it.asModel().atDate(date) } - .sortedBy { it.departureTime } + .sortedBy { it.time.departure } } call.respond(times) } diff --git a/server/src/main/kotlin/moe/lava/banksia/server/GtfsImporter.kt b/server/src/main/kotlin/moe/lava/banksia/server/GtfsImporter.kt index 17ab1f5..84fae70 100644 --- a/server/src/main/kotlin/moe/lava/banksia/server/GtfsImporter.kt +++ b/server/src/main/kotlin/moe/lava/banksia/server/GtfsImporter.kt @@ -6,7 +6,6 @@ import moe.lava.banksia.core.model.Service import moe.lava.banksia.core.model.ServiceException import moe.lava.banksia.core.model.Shape import moe.lava.banksia.core.model.Stop -import moe.lava.banksia.core.model.StopTime import moe.lava.banksia.core.model.Trip import moe.lava.banksia.core.sqld.DatabaseManager import moe.lava.banksia.core.sqld.mappers.asDb @@ -30,7 +29,6 @@ class GtfsImporter( is GtfsData.ServiceExceptionChunk -> database.addServiceExceptions(chunk.exceptions) is GtfsData.ShapeChunk -> database.addShapes(chunk.shapes) is GtfsData.StopChunk -> database.addStops(chunk.stops) - is GtfsData.StopTimeChunk -> database.addStopTimes(chunk.stopTimes) is GtfsData.TripChunk -> database.addTrips(chunk.trips) } } @@ -101,21 +99,15 @@ class GtfsImporter( log.info("done") } - private fun Database.addStopTimes(stopTimes: List) { - log.info("inserting ${stopTimes.size} stoptimes...") - stopTimeQueries.transaction { - stopTimes.forEach { - stopTimeQueries.insert(it.asDb()) - } - } - log.info("done") - } - - private fun Database.addTrips(trips: List) { + private fun Database.addTrips(trips: List) { log.info("inserting ${trips.size} trips...") - tripQueries.transaction { - trips.forEach { - tripQueries.insert(it.asDb()) + transaction { + trips.forEach { trip -> + stoppingPatternQueries.insert(trip.pattern.asDb()) + trip.pattern.stoptimes.forEach { stoptime -> + stopTimeQueries.insert(stoptime.asDb()) + } + tripQueries.insert(trip.asDb()) } } log.info("done") diff --git a/server/src/main/resources/logback.xml b/server/src/main/resources/logback.xml index de5d8bf..6519371 100644 --- a/server/src/main/resources/logback.xml +++ b/server/src/main/resources/logback.xml @@ -14,7 +14,7 @@ %d{YYYY-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n - + diff --git a/settings.gradle.kts b/settings.gradle.kts index 78831ff..28f535e 100644 --- a/settings.gradle.kts +++ b/settings.gradle.kts @@ -39,6 +39,7 @@ include(":core") include(":core:data") include(":core:data:client") include(":core:data:server") +include(":core:data:stoptime") include(":core:sqld") include(":ui") include(":ui:maps") diff --git a/ui/src/commonMain/kotlin/moe/lava/banksia/ui/screens/map/MapScreenViewModel.kt b/ui/src/commonMain/kotlin/moe/lava/banksia/ui/screens/map/MapScreenViewModel.kt index c4bd768..76fb51e 100644 --- a/ui/src/commonMain/kotlin/moe/lava/banksia/ui/screens/map/MapScreenViewModel.kt +++ b/ui/src/commonMain/kotlin/moe/lava/banksia/ui/screens/map/MapScreenViewModel.kt @@ -231,30 +231,36 @@ class MapScreenViewModel( ) } - val departures = stopTimeRepository.getForStop(id) - .filter { !it.headsign.isNullOrBlank() } - .groupBy { it.headsign!! } - .map { (headsign, stopTimes) -> - val now = Clock.System.now() - val times = stopTimes - .map { it.arrivalTime.toInstant(TimeZone.currentSystemDefault()) } - .filter { it >= (now - 1.minutes) } - .joinToString(" | ") { - val diff = (it - now).inWholeMinutes.coerceAtLeast(0) - if (diff >= 65) { - "${((diff + 30.0) / 60.0).toInt()}hr" - } else { - "${diff}mn" - } + stopTimeRepository.getForStop(id) + .onEach { stoptimes -> + val departures = stoptimes +// .filter { !it.headsign.isNullOrBlank() } +// .groupBy { it.headsign!! } + .groupBy { it.stopId } // TODO: Placeholder + .map { (headsign, stopTimes) -> + val now = Clock.System.now() + val times = stopTimes + .map { it.time.arrival.toInstant(TimeZone.currentSystemDefault()) } + .filter { it >= (now - 1.minutes) } + .joinToString(" | ") { + val diff = (it - now).inWholeMinutes.coerceAtLeast(0) + if (diff >= 65) { + "${((diff + 30.0) / 60.0).toInt()}hr" + } else { + "${diff}mn" + } + } + StopInfoPanelState.Departure(headsign, times) } - StopInfoPanelState.Departure(headsign, times) + + iInfoState.update { + if (it !is StopInfoPanelState) + it + else + it.copy(departures = departures) + } } - iInfoState.update { - if (it !is StopInfoPanelState) - it - else - it.copy(departures = departures) - } + .launchIn(viewModelScope) } /*private suspend fun buildPolylines(route: PtvRoute) {