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
This commit is contained in:
parent
f1770744db
commit
102c028407
39 changed files with 396 additions and 223 deletions
|
|
@ -27,6 +27,7 @@ kotlin {
|
||||||
sourceSets {
|
sourceSets {
|
||||||
commonMain.dependencies {
|
commonMain.dependencies {
|
||||||
implementation(projects.core)
|
implementation(projects.core)
|
||||||
|
api(projects.core.data.stoptime)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -10,16 +10,12 @@ import kotlinx.serialization.json.Json
|
||||||
import moe.lava.banksia.core.Constants
|
import moe.lava.banksia.core.Constants
|
||||||
import moe.lava.banksia.core.data.repositories.ClientRouteRepository
|
import moe.lava.banksia.core.data.repositories.ClientRouteRepository
|
||||||
import moe.lava.banksia.core.data.repositories.ClientStopRepository
|
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.RouteRepository
|
||||||
import moe.lava.banksia.core.data.repositories.StopRepository
|
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.RouteLocalDataSource
|
||||||
import moe.lava.banksia.core.data.sources.route.RouteRemoteDataSource
|
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.StopLocalDataSource
|
||||||
import moe.lava.banksia.core.data.sources.stop.StopRemoteDataSource
|
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.sqld.sqldDiModule
|
||||||
import moe.lava.banksia.core.util.log
|
import moe.lava.banksia.core.util.log
|
||||||
import moe.lava.banksia.data.ptv.PtvService
|
import moe.lava.banksia.data.ptv.PtvService
|
||||||
|
|
@ -29,6 +25,7 @@ import org.koin.dsl.module
|
||||||
|
|
||||||
val clientDataDiModule = module {
|
val clientDataDiModule = module {
|
||||||
includes(sqldDiModule)
|
includes(sqldDiModule)
|
||||||
|
includes(stopTimeDataDiModule)
|
||||||
|
|
||||||
// HTTP Clients
|
// HTTP Clients
|
||||||
singleOf(::PtvService)
|
singleOf(::PtvService)
|
||||||
|
|
@ -56,11 +53,8 @@ val clientDataDiModule = module {
|
||||||
singleOf(::RouteRemoteDataSource)
|
singleOf(::RouteRemoteDataSource)
|
||||||
singleOf(::StopLocalDataSource)
|
singleOf(::StopLocalDataSource)
|
||||||
singleOf(::StopRemoteDataSource)
|
singleOf(::StopRemoteDataSource)
|
||||||
singleOf(::StopTimeLocalDataSource)
|
|
||||||
singleOf(::StopTimeRemoteDataSource)
|
|
||||||
|
|
||||||
// Repositories
|
// Repositories
|
||||||
singleOf(::ClientRouteRepository) bind RouteRepository::class
|
singleOf(::ClientRouteRepository) bind RouteRepository::class
|
||||||
singleOf(::ClientStopRepository) bind StopRepository::class
|
singleOf(::ClientStopRepository) bind StopRepository::class
|
||||||
singleOf(::ClientStopTimeRepository) bind StopTimeRepository::class
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -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<StopTimeDated> {
|
|
||||||
return local
|
|
||||||
.getAtStop(id)
|
|
||||||
.ifEmpty { remote.getAtStop(id) }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -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<Trip> {
|
|
||||||
return listOf()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -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<StopTimeDated>
|
|
||||||
}
|
|
||||||
60
core/data/stoptime/build.gradle.kts
Normal file
60
core/data/stoptime/build.gradle.kts
Normal file
|
|
@ -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)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -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)
|
||||||
|
}
|
||||||
|
|
@ -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) }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -7,7 +7,7 @@ import io.ktor.client.request.parameter
|
||||||
import kotlinx.datetime.LocalDate
|
import kotlinx.datetime.LocalDate
|
||||||
import kotlinx.datetime.TimeZone
|
import kotlinx.datetime.TimeZone
|
||||||
import kotlinx.datetime.todayIn
|
import kotlinx.datetime.todayIn
|
||||||
import moe.lava.banksia.core.model.StopTimeDated
|
import moe.lava.banksia.core.model.StopTime
|
||||||
import kotlin.time.Clock
|
import kotlin.time.Clock
|
||||||
|
|
||||||
internal class StopTimeRemoteDataSource(
|
internal class StopTimeRemoteDataSource(
|
||||||
|
|
@ -16,21 +16,9 @@ internal class StopTimeRemoteDataSource(
|
||||||
suspend fun getAtStop(
|
suspend fun getAtStop(
|
||||||
stopId: String,
|
stopId: String,
|
||||||
date: LocalDate? = Clock.System.todayIn(TimeZone.currentSystemDefault()),
|
date: LocalDate? = Clock.System.todayIn(TimeZone.currentSystemDefault()),
|
||||||
): List<StopTimeDated> {
|
): List<StopTime.Dated> {
|
||||||
return client.get("stoptimes/by_stop/${stopId}") {
|
return client.get("stoptimes/by_stop/${stopId}") {
|
||||||
parameter("date", date)
|
parameter("date", date)
|
||||||
}.body<List<StopTimeDated>>()
|
}.body<List<StopTime.Dated>>()
|
||||||
}
|
}
|
||||||
|
|
||||||
/*suspend fun get(
|
|
||||||
stop: String? = null,
|
|
||||||
trip: String? = null,
|
|
||||||
day: DayOfWeek? = Clock.System.todayIn(TimeZone.currentSystemDefault()).dayOfWeek,
|
|
||||||
): List<StopTime> {
|
|
||||||
return client.get("stoptimes") {
|
|
||||||
stop?.let { parameter("stop", it) }
|
|
||||||
trip?.let { parameter("trip", it) }
|
|
||||||
day?.let { parameter("day", it) }
|
|
||||||
}.body<List<StopTime>>()
|
|
||||||
}*/
|
|
||||||
}
|
}
|
||||||
|
|
@ -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)
|
||||||
|
}
|
||||||
|
|
@ -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<List<StopTime.Dated>>
|
||||||
|
}
|
||||||
|
|
@ -4,23 +4,19 @@ import kotlinx.coroutines.Dispatchers
|
||||||
import kotlinx.coroutines.IO
|
import kotlinx.coroutines.IO
|
||||||
import kotlinx.coroutines.withContext
|
import kotlinx.coroutines.withContext
|
||||||
import kotlinx.datetime.LocalDate
|
import kotlinx.datetime.LocalDate
|
||||||
import kotlinx.datetime.TimeZone
|
import moe.lava.banksia.core.model.StopTime
|
||||||
import kotlinx.datetime.todayIn
|
|
||||||
import moe.lava.banksia.core.model.StopTimeDated
|
|
||||||
import moe.lava.banksia.core.model.atDate
|
import moe.lava.banksia.core.model.atDate
|
||||||
import moe.lava.banksia.core.sqld.StopTimeQueries
|
import moe.lava.banksia.core.sqld.StopTimeQueries
|
||||||
import moe.lava.banksia.core.sqld.mappers.asModel
|
import moe.lava.banksia.core.sqld.mappers.asModel
|
||||||
import moe.lava.banksia.core.util.serialise
|
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(
|
internal class StopTimeLocalDataSource : KoinComponent {
|
||||||
private val queries: StopTimeQueries,
|
private val queries get() = get<StopTimeQueries>()
|
||||||
) {
|
|
||||||
suspend fun getAtStop(
|
suspend fun getAtStop(stopId: String, date: LocalDate): List<StopTime.Dated> {
|
||||||
stopId: String,
|
return withContext(context = Dispatchers.IO) {
|
||||||
date: LocalDate = Clock.System.todayIn(TimeZone.currentSystemDefault()),
|
|
||||||
): List<StopTimeDated> {
|
|
||||||
return withContext(Dispatchers.IO) {
|
|
||||||
queries
|
queries
|
||||||
.getForStopDated(
|
.getForStopDated(
|
||||||
listOf(date.dayOfWeek).serialise().toLong(),
|
listOf(date.dayOfWeek).serialise().toLong(),
|
||||||
|
|
@ -29,7 +25,7 @@ internal class StopTimeLocalDataSource(
|
||||||
)
|
)
|
||||||
.executeAsList()
|
.executeAsList()
|
||||||
.map { it.asModel().atDate(date) }
|
.map { it.asModel().atDate(date) }
|
||||||
.sortedBy { it.departureTime }
|
.sortedBy { it.time.departure }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -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)
|
||||||
|
}
|
||||||
|
|
@ -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))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -47,6 +47,7 @@ sqldelight {
|
||||||
databases {
|
databases {
|
||||||
register("BanksiaDatabase") {
|
register("BanksiaDatabase") {
|
||||||
packageName.set("moe.lava.banksia.core.sqld")
|
packageName.set("moe.lava.banksia.core.sqld")
|
||||||
|
schemaOutputDirectory.set(file("src/commonMain/sqldelight/schema"))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -5,10 +5,10 @@ import app.cash.sqldelight.driver.android.AndroidSqliteDriver
|
||||||
import org.koin.core.component.KoinComponent
|
import org.koin.core.component.KoinComponent
|
||||||
import org.koin.core.component.get
|
import org.koin.core.component.get
|
||||||
|
|
||||||
actual class DatabaseManager actual constructor() : KoinComponent {
|
actual class DatabaseManager : KoinComponent {
|
||||||
actual val database by lazy {
|
actual val database by lazy {
|
||||||
val ctx = get<Context>().applicationContext
|
val ctx = get<Context>().applicationContext
|
||||||
val driver = AndroidSqliteDriver(BanksiaDatabase.Schema, ctx, "timetable.db")
|
val driver = AndroidSqliteDriver(BanksiaDatabase.Schema, ctx, "${DBNAME}.db")
|
||||||
BanksiaDatabase(driver)
|
BanksiaDatabase(driver)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,7 @@
|
||||||
package moe.lava.banksia.core.sqld
|
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
|
val database: BanksiaDatabase
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -11,6 +11,7 @@ val sqldDiModule = module {
|
||||||
factory { get<BanksiaDatabase>().serviceExceptionQueries }
|
factory { get<BanksiaDatabase>().serviceExceptionQueries }
|
||||||
factory { get<BanksiaDatabase>().shapeQueries }
|
factory { get<BanksiaDatabase>().shapeQueries }
|
||||||
factory { get<BanksiaDatabase>().stopQueries }
|
factory { get<BanksiaDatabase>().stopQueries }
|
||||||
|
factory { get<BanksiaDatabase>().stoppingPatternQueries }
|
||||||
factory { get<BanksiaDatabase>().stopTimeQueries }
|
factory { get<BanksiaDatabase>().stopTimeQueries }
|
||||||
factory { get<BanksiaDatabase>().tripQueries }
|
factory { get<BanksiaDatabase>().tripQueries }
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -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
|
||||||
import moe.lava.banksia.core.model.FutureTime.Companion.asInt
|
import moe.lava.banksia.core.model.FutureTime.Companion.asInt
|
||||||
import moe.lava.banksia.core.model.StopTime
|
import moe.lava.banksia.core.model.StopTime
|
||||||
|
import moe.lava.banksia.core.model.TimeType
|
||||||
import moe.lava.banksia.core.sqld.StopTime as DbStopTime
|
import moe.lava.banksia.core.sqld.StopTime as DbStopTime
|
||||||
|
|
||||||
fun DbStopTime.asModel() = StopTime(
|
fun DbStopTime.asModel() = StopTime(
|
||||||
tripId = tripId,
|
patternId = patternId,
|
||||||
stopId = stopId,
|
stopId = stopId,
|
||||||
arrivalTime = FutureTime.fromInt(arrivalTime.toInt()),
|
time = TimeType.Undated(
|
||||||
departureTime = FutureTime.fromInt(departureTime.toInt()),
|
arrival = FutureTime.fromInt((departureTime + arrivalDelta).toInt()),
|
||||||
headsign = null,
|
departure = FutureTime.fromInt(departureTime.toInt()),
|
||||||
|
),
|
||||||
pickupType = pickupType.toInt(),
|
pickupType = pickupType.toInt(),
|
||||||
dropOffType = dropOffType.toInt(),
|
dropOffType = dropOffType.toInt(),
|
||||||
)
|
)
|
||||||
|
|
||||||
fun StopTime.asDb() = DbStopTime(
|
fun StopTime.Undated.asDb() = DbStopTime(
|
||||||
tripId = tripId,
|
patternId = patternId,
|
||||||
stopId = stopId,
|
stopId = stopId,
|
||||||
arrivalTime = arrivalTime.asInt().toLong(),
|
arrivalDelta = (time.arrival.asInt() - time.departure.asInt()).toLong(),
|
||||||
departureTime = departureTime.asInt().toLong(),
|
departureTime = time.departure.asInt().toLong(),
|
||||||
pickupType = pickupType.toLong(),
|
pickupType = pickupType.toLong(),
|
||||||
dropOffType = dropOffType.toLong(),
|
dropOffType = dropOffType.toLong(),
|
||||||
)
|
)
|
||||||
|
|
|
||||||
|
|
@ -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<StopTime.Undated>) = 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,
|
||||||
|
)
|
||||||
|
|
@ -1,32 +1,27 @@
|
||||||
package moe.lava.banksia.core.sqld.mappers
|
package moe.lava.banksia.core.sqld.mappers
|
||||||
|
|
||||||
import moe.lava.banksia.core.model.Service
|
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.model.Trip
|
||||||
import moe.lava.banksia.core.sqld.Trip as DbTrip
|
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) {
|
if (serviceId != service.id) {
|
||||||
throw IllegalArgumentException("trip and service id mismatch (${serviceId} != ${service.id})")
|
throw IllegalArgumentException("trip and service id mismatch (${serviceId} != ${service.id})")
|
||||||
}
|
}
|
||||||
return Trip(
|
return Trip(
|
||||||
id = id,
|
id = gtfsId,
|
||||||
routeId = routeId,
|
pattern = pattern,
|
||||||
service = service,
|
service = service,
|
||||||
shapeId = shapeId,
|
directionId = directionId.toInt(),
|
||||||
tripHeadsign = tripHeadsign,
|
blockId = blockId.toString(),
|
||||||
directionId = directionId,
|
|
||||||
blockId = blockId,
|
|
||||||
wheelchairAccessible = wheelchairAccessible == 1L
|
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
fun Trip.asDb() = DbTrip(
|
fun Trip.Undated.asDb() = DbTrip(
|
||||||
id = id,
|
gtfsId = id,
|
||||||
routeId = routeId,
|
patternId = pattern.id,
|
||||||
serviceId = service.id,
|
serviceId = service.id,
|
||||||
shapeId = shapeId,
|
directionId = directionId.toLong(),
|
||||||
tripHeadsign = tripHeadsign,
|
blockId = blockId?.toLong(),
|
||||||
directionId = directionId,
|
|
||||||
blockId = blockId,
|
|
||||||
wheelchairAccessible = if (wheelchairAccessible) 1L else 0L
|
|
||||||
)
|
)
|
||||||
|
|
|
||||||
|
|
@ -15,7 +15,7 @@ getAll:
|
||||||
SELECT * FROM Stop;
|
SELECT * FROM Stop;
|
||||||
|
|
||||||
getAllParentless:
|
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:
|
get:
|
||||||
SELECT * FROM Stop WHERE id == ?;
|
SELECT * FROM Stop WHERE id == ?;
|
||||||
|
|
@ -32,8 +32,8 @@ UPDATE Stop SET parent = ? WHERE id IN ?;
|
||||||
getByRoute:
|
getByRoute:
|
||||||
SELECT Stop.* FROM Stop
|
SELECT Stop.* FROM Stop
|
||||||
INNER JOIN StopTime ON StopTime.stopId == Stop.id
|
INNER JOIN StopTime ON StopTime.stopId == Stop.id
|
||||||
INNER JOIN Trip ON Trip.id == StopTime.tripId
|
INNER JOIN StoppingPattern ON StoppingPattern.id == StopTime.patternId
|
||||||
WHERE Trip.routeId == :id
|
WHERE StoppingPattern.routeId == :id
|
||||||
GROUP BY Stop.id;
|
GROUP BY Stop.id;
|
||||||
|
|
||||||
-- I vibecoded this, sorry
|
-- I vibecoded this, sorry
|
||||||
|
|
@ -41,8 +41,8 @@ getParentsByRoute:
|
||||||
WITH RECURSIVE Tree AS (
|
WITH RECURSIVE Tree AS (
|
||||||
SELECT Stop.* FROM Stop
|
SELECT Stop.* FROM Stop
|
||||||
INNER JOIN StopTime ON StopTime.stopId == Stop.id
|
INNER JOIN StopTime ON StopTime.stopId == Stop.id
|
||||||
INNER JOIN Trip ON Trip.id == StopTime.tripId
|
INNER JOIN StoppingPattern ON StoppingPattern.id == StopTime.patternId
|
||||||
WHERE Trip.routeId == :id
|
WHERE StoppingPattern.routeId == :id
|
||||||
GROUP BY Stop.id
|
GROUP BY Stop.id
|
||||||
|
|
||||||
UNION ALL
|
UNION ALL
|
||||||
|
|
|
||||||
|
|
@ -1,11 +1,11 @@
|
||||||
CREATE TABLE StopTime (
|
CREATE TABLE StopTime (
|
||||||
tripId TEXT NOT NULL REFERENCES Trip (id),
|
patternId INTEGER NOT NULL REFERENCES StoppingPattern (id),
|
||||||
stopId TEXT NOT NULL REFERENCES Stop (id),
|
stopId TEXT NOT NULL REFERENCES Stop (id),
|
||||||
arrivalTime INTEGER NOT NULL,
|
arrivalDelta INTEGER NOT NULL,
|
||||||
departureTime INTEGER NOT NULL,
|
departureTime INTEGER NOT NULL,
|
||||||
pickupType INTEGER NOT NULL,
|
pickupType INTEGER NOT NULL,
|
||||||
dropOffType INTEGER NOT NULL,
|
dropOffType INTEGER NOT NULL,
|
||||||
PRIMARY KEY (tripId, stopId)
|
PRIMARY KEY (patternId, stopId)
|
||||||
) WITHOUT ROWID;
|
) WITHOUT ROWID;
|
||||||
|
|
||||||
CREATE INDEX idx_StopTime_stopId ON StopTime (stopId);
|
CREATE INDEX idx_StopTime_stopId ON StopTime (stopId);
|
||||||
|
|
@ -16,8 +16,9 @@ INSERT OR REPLACE INTO StopTime VALUES ?;
|
||||||
getForStopDated:
|
getForStopDated:
|
||||||
SELECT DISTINCT StopTime.* FROM StopTime
|
SELECT DISTINCT StopTime.* FROM StopTime
|
||||||
INNER JOIN Service ON Service.days & :days = :days AND :date BETWEEN Service.start AND Service.`end`
|
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
|
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 StopTime.stopId IN (SELECT Stop.id FROM Stop WHERE Stop.parent == :stopId OR Stop.id == :stopId)
|
||||||
AND ServiceException.type IS NULL;
|
AND ServiceException.type IS NULL;
|
||||||
|
|
|
||||||
|
|
@ -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 ?;
|
||||||
|
|
@ -1,14 +1,12 @@
|
||||||
CREATE TABLE Trip (
|
CREATE TABLE Trip (
|
||||||
id TEXT PRIMARY KEY NOT NULL,
|
gtfsId TEXT PRIMARY KEY NOT NULL,
|
||||||
routeId TEXT NOT NULL REFERENCES Route (id),
|
patternId INTEGER NOT NULL REFERENCES StoppingPattern (id),
|
||||||
serviceId TEXT NOT NULL REFERENCES Service (id),
|
serviceId TEXT NOT NULL REFERENCES Service (id),
|
||||||
shapeId TEXT NOT NULL REFERENCES Shape (id),
|
blockId INTEGER,
|
||||||
tripHeadsign TEXT NOT NULL,
|
directionId INTEGER NOT NULL
|
||||||
directionId TEXT NOT NULL,
|
|
||||||
blockId TEXT,
|
|
||||||
wheelchairAccessible INTEGER NOT NULL
|
|
||||||
);
|
);
|
||||||
|
|
||||||
|
CREATE INDEX idx_Trip_patternId ON Trip (patternId);
|
||||||
CREATE INDEX idx_Trip_serviceId ON Trip (serviceId);
|
CREATE INDEX idx_Trip_serviceId ON Trip (serviceId);
|
||||||
|
|
||||||
insert:
|
insert:
|
||||||
|
|
|
||||||
BIN
core/sqld/src/commonMain/sqldelight/schema/1.db
Normal file
BIN
core/sqld/src/commonMain/sqldelight/schema/1.db
Normal file
Binary file not shown.
|
|
@ -1,10 +1,11 @@
|
||||||
package moe.lava.banksia.core.sqld
|
package moe.lava.banksia.core.sqld
|
||||||
|
|
||||||
import app.cash.sqldelight.driver.native.NativeSqliteDriver
|
import app.cash.sqldelight.driver.native.NativeSqliteDriver
|
||||||
|
import org.koin.core.component.KoinComponent
|
||||||
|
|
||||||
actual class DatabaseManager actual constructor() : org.koin.core.component.KoinComponent {
|
actual class DatabaseManager : KoinComponent {
|
||||||
actual val database by lazy {
|
actual val database by lazy {
|
||||||
val driver = NativeSqliteDriver(BanksiaDatabase.Schema, "timetable.db")
|
val driver = NativeSqliteDriver(BanksiaDatabase.Schema, "${DBNAME}.db")
|
||||||
BanksiaDatabase(driver)
|
BanksiaDatabase(driver)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -11,9 +11,7 @@ import java.io.File
|
||||||
import java.util.Properties
|
import java.util.Properties
|
||||||
import kotlin.system.exitProcess
|
import kotlin.system.exitProcess
|
||||||
|
|
||||||
private const val DBNAME = "timetable"
|
actual class DatabaseManager : KoinComponent {
|
||||||
|
|
||||||
actual class DatabaseManager actual constructor() : KoinComponent {
|
|
||||||
private var driver = connect()
|
private var driver = connect()
|
||||||
actual val database get() = BanksiaDatabase(driver)
|
actual val database get() = BanksiaDatabase(driver)
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,14 +1,43 @@
|
||||||
package moe.lava.banksia.core.model
|
package moe.lava.banksia.core.model
|
||||||
|
|
||||||
|
import kotlinx.datetime.LocalDate
|
||||||
|
import kotlinx.datetime.LocalDateTime
|
||||||
import kotlinx.serialization.Serializable
|
import kotlinx.serialization.Serializable
|
||||||
|
|
||||||
@Serializable
|
@Serializable
|
||||||
data class StopTime(
|
data class StopTime<T: TimeType>(
|
||||||
val tripId: String,
|
val patternId: Long,
|
||||||
val stopId: String,
|
val stopId: String,
|
||||||
val arrivalTime: FutureTime,
|
val time: T,
|
||||||
val departureTime: FutureTime,
|
|
||||||
val headsign: String?,
|
|
||||||
val pickupType: Int,
|
val pickupType: Int,
|
||||||
val dropOffType: Int,
|
val dropOffType: Int,
|
||||||
|
) {
|
||||||
|
typealias Dated = StopTime<TimeType.Dated>
|
||||||
|
typealias Undated = StopTime<TimeType.Undated>
|
||||||
|
}
|
||||||
|
|
||||||
|
@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<TimeType.Undated>.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,
|
||||||
)
|
)
|
||||||
|
|
|
||||||
|
|
@ -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,
|
|
||||||
)
|
|
||||||
|
|
@ -0,0 +1,16 @@
|
||||||
|
package moe.lava.banksia.core.model
|
||||||
|
|
||||||
|
import kotlinx.serialization.Serializable
|
||||||
|
|
||||||
|
@Serializable
|
||||||
|
data class StoppingPattern<T: TimeType>(
|
||||||
|
val id: Long,
|
||||||
|
val routeId: String,
|
||||||
|
val shapeId: String,
|
||||||
|
val headsign: String,
|
||||||
|
val wheelchairAccessible: Boolean,
|
||||||
|
val stoptimes: List<StopTime<T>>,
|
||||||
|
) {
|
||||||
|
typealias Dated = StoppingPattern<TimeType.Dated>
|
||||||
|
typealias Undated = StoppingPattern<TimeType.Undated>
|
||||||
|
}
|
||||||
|
|
@ -3,13 +3,13 @@ package moe.lava.banksia.core.model
|
||||||
import kotlinx.serialization.Serializable
|
import kotlinx.serialization.Serializable
|
||||||
|
|
||||||
@Serializable
|
@Serializable
|
||||||
data class Trip(
|
data class Trip<T: TimeType>(
|
||||||
val id: String,
|
val id: String,
|
||||||
val routeId: String,
|
val pattern: StoppingPattern<T>,
|
||||||
val service: Service,
|
val service: Service,
|
||||||
val shapeId: String,
|
val directionId: Int,
|
||||||
val tripHeadsign: String,
|
|
||||||
val directionId: String,
|
|
||||||
val blockId: String?,
|
val blockId: String?,
|
||||||
val wheelchairAccessible: Boolean,
|
) {
|
||||||
)
|
typealias Dated = Trip<TimeType.Dated>
|
||||||
|
typealias Undated = Trip<TimeType.Undated>
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -18,6 +18,7 @@ import kotlinx.serialization.decodeFromString
|
||||||
import kotlinx.serialization.modules.EmptySerializersModule
|
import kotlinx.serialization.modules.EmptySerializersModule
|
||||||
import kotlinx.serialization.serializer
|
import kotlinx.serialization.serializer
|
||||||
import moe.lava.banksia.core.Constants
|
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.Route
|
||||||
import moe.lava.banksia.core.model.RouteType
|
import moe.lava.banksia.core.model.RouteType
|
||||||
import moe.lava.banksia.core.model.Service
|
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.Shape
|
||||||
import moe.lava.banksia.core.model.Stop
|
import moe.lava.banksia.core.model.Stop
|
||||||
import moe.lava.banksia.core.model.StopTime
|
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.model.Trip
|
||||||
import moe.lava.banksia.core.util.Point
|
import moe.lava.banksia.core.util.Point
|
||||||
import moe.lava.banksia.server.gtfs.structures.GtfsRoute
|
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.GtfsStopTime
|
||||||
import moe.lava.banksia.server.gtfs.structures.GtfsTrip
|
import moe.lava.banksia.server.gtfs.structures.GtfsTrip
|
||||||
import java.io.File
|
import java.io.File
|
||||||
|
import java.nio.ByteBuffer
|
||||||
|
import java.security.MessageDigest
|
||||||
import java.util.zip.ZipFile
|
import java.util.zip.ZipFile
|
||||||
import kotlin.time.ExperimentalTime
|
import kotlin.time.ExperimentalTime
|
||||||
|
|
||||||
|
|
@ -46,8 +51,7 @@ sealed class GtfsData {
|
||||||
data class ServiceExceptionChunk(val exceptions: List<ServiceException>) : GtfsData()
|
data class ServiceExceptionChunk(val exceptions: List<ServiceException>) : GtfsData()
|
||||||
data class ShapeChunk(val shapes: List<Shape>) : GtfsData()
|
data class ShapeChunk(val shapes: List<Shape>) : GtfsData()
|
||||||
data class StopChunk(val stops: List<Stop>) : GtfsData()
|
data class StopChunk(val stops: List<Stop>) : GtfsData()
|
||||||
data class StopTimeChunk(val stopTimes: List<StopTime>) : GtfsData()
|
data class TripChunk(val trips: List<Trip.Undated>) : GtfsData()
|
||||||
data class TripChunk(val trips: List<Trip>) : GtfsData()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
class GtfsParser(
|
class GtfsParser(
|
||||||
|
|
@ -129,7 +133,6 @@ class GtfsParser(
|
||||||
.filter { it.name == "trips.txt" }
|
.filter { it.name == "trips.txt" }
|
||||||
.flatMap { fd ->
|
.flatMap { fd ->
|
||||||
parseTrips(fd, services)
|
parseTrips(fd, services)
|
||||||
.also { emit(GtfsData.TripChunk(it)) }
|
|
||||||
}
|
}
|
||||||
.associateBy { it.id }
|
.associateBy { it.id }
|
||||||
|
|
||||||
|
|
@ -137,13 +140,53 @@ class GtfsParser(
|
||||||
.filter { it.name == "stop_times.txt" }
|
.filter { it.name == "stop_times.txt" }
|
||||||
.forEach { fd ->
|
.forEach { fd ->
|
||||||
log.info("parsing stop times for ${fd.parent}...")
|
log.info("parsing stop times for ${fd.parent}...")
|
||||||
parseStopTimes(fd, trips) { seq ->
|
parseStopTimes(fd) { seq ->
|
||||||
seq.chunked(1000000)
|
val times = ArrayList<Pair<String, StopTime.Undated>>(1000100)
|
||||||
.forEach { emit(GtfsData.StopTimeChunk(it)) }
|
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<StopTime.Undated>): 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<String, Trip.Undated>, times: ArrayList<Pair<String, StopTime.Undated>>) =
|
||||||
|
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) =
|
private fun parseRoutes(fd: File) =
|
||||||
fd.parseCsv<GtfsRoute>()
|
fd.parseCsv<GtfsRoute>()
|
||||||
.map { with(it) {
|
.map { with(it) {
|
||||||
|
|
@ -180,16 +223,17 @@ class GtfsParser(
|
||||||
)
|
)
|
||||||
} }
|
} }
|
||||||
|
|
||||||
private inline fun parseStopTimes(fd: File, trips: Map<String, Trip>, block: (Sequence<StopTime>) -> Unit) =
|
private inline fun parseStopTimes(fd: File, block: (Sequence<Pair<String, StopTime.Undated>>) -> Unit) =
|
||||||
fd.parseCsvSequence<GtfsStopTime> { seq ->
|
fd.parseCsvSequence<GtfsStopTime> { seq ->
|
||||||
seq
|
seq
|
||||||
.map { with(it) {
|
.map { with(it) {
|
||||||
StopTime(
|
it.trip_id to StopTime(
|
||||||
tripId = trip_id,
|
patternId = stop_sequence,
|
||||||
stopId = stop_id,
|
stopId = stop_id,
|
||||||
arrivalTime = GtfsStopTime.parseGtfsTime(arrival_time),
|
time = TimeType.Undated(
|
||||||
departureTime = GtfsStopTime.parseGtfsTime(departure_time),
|
arrival = GtfsStopTime.parseGtfsTime(arrival_time),
|
||||||
headsign = stop_headsign.ifEmpty { trips[trip_id]!!.tripHeadsign },
|
departure = GtfsStopTime.parseGtfsTime(departure_time),
|
||||||
|
),
|
||||||
pickupType = pickup_type,
|
pickupType = pickup_type,
|
||||||
dropOffType = drop_off_type,
|
dropOffType = drop_off_type,
|
||||||
)
|
)
|
||||||
|
|
@ -230,15 +274,19 @@ class GtfsParser(
|
||||||
private fun parseTrips(fd: File, services: Map<String, Service>) =
|
private fun parseTrips(fd: File, services: Map<String, Service>) =
|
||||||
fd.parseCsv<GtfsTrip>()
|
fd.parseCsv<GtfsTrip>()
|
||||||
.map { with(it) {
|
.map { with(it) {
|
||||||
Trip(
|
Trip.Undated(
|
||||||
id = trip_id,
|
id = trip_id,
|
||||||
|
pattern = StoppingPattern(
|
||||||
|
id = 0,
|
||||||
routeId = route_id,
|
routeId = route_id,
|
||||||
service = services["${fd.parentFile.name}_${service_id}"]!!,
|
|
||||||
shapeId = shape_id,
|
shapeId = shape_id,
|
||||||
tripHeadsign = trip_headsign,
|
headsign = trip_headsign,
|
||||||
directionId = direction_id,
|
|
||||||
blockId = block_id.ifEmpty { null },
|
|
||||||
wheelchairAccessible = wheelchair_accessible == "1",
|
wheelchairAccessible = wheelchair_accessible == "1",
|
||||||
|
stoptimes = listOf()
|
||||||
|
),
|
||||||
|
service = services["${fd.parentFile.name}_${service_id}"]!!,
|
||||||
|
directionId = direction_id.toInt(),
|
||||||
|
blockId = block_id.ifEmpty { null },
|
||||||
)
|
)
|
||||||
} }
|
} }
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -10,7 +10,7 @@ internal data class GtfsStopTime(
|
||||||
val arrival_time: String,
|
val arrival_time: String,
|
||||||
val departure_time: String,
|
val departure_time: String,
|
||||||
val stop_id: String,
|
val stop_id: String,
|
||||||
val stop_sequence: Int,
|
val stop_sequence: Long,
|
||||||
val stop_headsign: String,
|
val stop_headsign: String,
|
||||||
val pickup_type: Int,
|
val pickup_type: Int,
|
||||||
val drop_off_type: Int,
|
val drop_off_type: Int,
|
||||||
|
|
|
||||||
|
|
@ -151,7 +151,7 @@ fun Application.module() {
|
||||||
)
|
)
|
||||||
.executeAsList()
|
.executeAsList()
|
||||||
.map { it.asModel().atDate(date) }
|
.map { it.asModel().atDate(date) }
|
||||||
.sortedBy { it.departureTime }
|
.sortedBy { it.time.departure }
|
||||||
}
|
}
|
||||||
call.respond(times)
|
call.respond(times)
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -6,7 +6,6 @@ import moe.lava.banksia.core.model.Service
|
||||||
import moe.lava.banksia.core.model.ServiceException
|
import moe.lava.banksia.core.model.ServiceException
|
||||||
import moe.lava.banksia.core.model.Shape
|
import moe.lava.banksia.core.model.Shape
|
||||||
import moe.lava.banksia.core.model.Stop
|
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.model.Trip
|
||||||
import moe.lava.banksia.core.sqld.DatabaseManager
|
import moe.lava.banksia.core.sqld.DatabaseManager
|
||||||
import moe.lava.banksia.core.sqld.mappers.asDb
|
import moe.lava.banksia.core.sqld.mappers.asDb
|
||||||
|
|
@ -30,7 +29,6 @@ class GtfsImporter(
|
||||||
is GtfsData.ServiceExceptionChunk -> database.addServiceExceptions(chunk.exceptions)
|
is GtfsData.ServiceExceptionChunk -> database.addServiceExceptions(chunk.exceptions)
|
||||||
is GtfsData.ShapeChunk -> database.addShapes(chunk.shapes)
|
is GtfsData.ShapeChunk -> database.addShapes(chunk.shapes)
|
||||||
is GtfsData.StopChunk -> database.addStops(chunk.stops)
|
is GtfsData.StopChunk -> database.addStops(chunk.stops)
|
||||||
is GtfsData.StopTimeChunk -> database.addStopTimes(chunk.stopTimes)
|
|
||||||
is GtfsData.TripChunk -> database.addTrips(chunk.trips)
|
is GtfsData.TripChunk -> database.addTrips(chunk.trips)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -101,21 +99,15 @@ class GtfsImporter(
|
||||||
log.info("done")
|
log.info("done")
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun Database.addStopTimes(stopTimes: List<StopTime>) {
|
private fun Database.addTrips(trips: List<Trip.Undated>) {
|
||||||
log.info("inserting ${stopTimes.size} stoptimes...")
|
|
||||||
stopTimeQueries.transaction {
|
|
||||||
stopTimes.forEach {
|
|
||||||
stopTimeQueries.insert(it.asDb())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
log.info("done")
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun Database.addTrips(trips: List<Trip>) {
|
|
||||||
log.info("inserting ${trips.size} trips...")
|
log.info("inserting ${trips.size} trips...")
|
||||||
tripQueries.transaction {
|
transaction {
|
||||||
trips.forEach {
|
trips.forEach { trip ->
|
||||||
tripQueries.insert(it.asDb())
|
stoppingPatternQueries.insert(trip.pattern.asDb())
|
||||||
|
trip.pattern.stoptimes.forEach { stoptime ->
|
||||||
|
stopTimeQueries.insert(stoptime.asDb())
|
||||||
|
}
|
||||||
|
tripQueries.insert(trip.asDb())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
log.info("done")
|
log.info("done")
|
||||||
|
|
|
||||||
|
|
@ -14,7 +14,7 @@
|
||||||
<pattern>%d{YYYY-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n</pattern>
|
<pattern>%d{YYYY-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n</pattern>
|
||||||
</encoder>
|
</encoder>
|
||||||
</appender>
|
</appender>
|
||||||
<root level="trace">
|
<root level="debug">
|
||||||
<appender-ref ref="FILE"/>
|
<appender-ref ref="FILE"/>
|
||||||
<appender-ref ref="STDOUT"/>
|
<appender-ref ref="STDOUT"/>
|
||||||
</root>
|
</root>
|
||||||
|
|
|
||||||
|
|
@ -39,6 +39,7 @@ include(":core")
|
||||||
include(":core:data")
|
include(":core:data")
|
||||||
include(":core:data:client")
|
include(":core:data:client")
|
||||||
include(":core:data:server")
|
include(":core:data:server")
|
||||||
|
include(":core:data:stoptime")
|
||||||
include(":core:sqld")
|
include(":core:sqld")
|
||||||
include(":ui")
|
include(":ui")
|
||||||
include(":ui:maps")
|
include(":ui:maps")
|
||||||
|
|
|
||||||
|
|
@ -231,13 +231,16 @@ class MapScreenViewModel(
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
val departures = stopTimeRepository.getForStop(id)
|
stopTimeRepository.getForStop(id)
|
||||||
.filter { !it.headsign.isNullOrBlank() }
|
.onEach { stoptimes ->
|
||||||
.groupBy { it.headsign!! }
|
val departures = stoptimes
|
||||||
|
// .filter { !it.headsign.isNullOrBlank() }
|
||||||
|
// .groupBy { it.headsign!! }
|
||||||
|
.groupBy { it.stopId } // TODO: Placeholder
|
||||||
.map { (headsign, stopTimes) ->
|
.map { (headsign, stopTimes) ->
|
||||||
val now = Clock.System.now()
|
val now = Clock.System.now()
|
||||||
val times = stopTimes
|
val times = stopTimes
|
||||||
.map { it.arrivalTime.toInstant(TimeZone.currentSystemDefault()) }
|
.map { it.time.arrival.toInstant(TimeZone.currentSystemDefault()) }
|
||||||
.filter { it >= (now - 1.minutes) }
|
.filter { it >= (now - 1.minutes) }
|
||||||
.joinToString(" | ") {
|
.joinToString(" | ") {
|
||||||
val diff = (it - now).inWholeMinutes.coerceAtLeast(0)
|
val diff = (it - now).inWholeMinutes.coerceAtLeast(0)
|
||||||
|
|
@ -249,6 +252,7 @@ class MapScreenViewModel(
|
||||||
}
|
}
|
||||||
StopInfoPanelState.Departure(headsign, times)
|
StopInfoPanelState.Departure(headsign, times)
|
||||||
}
|
}
|
||||||
|
|
||||||
iInfoState.update {
|
iInfoState.update {
|
||||||
if (it !is StopInfoPanelState)
|
if (it !is StopInfoPanelState)
|
||||||
it
|
it
|
||||||
|
|
@ -256,6 +260,8 @@ class MapScreenViewModel(
|
||||||
it.copy(departures = departures)
|
it.copy(departures = departures)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
.launchIn(viewModelScope)
|
||||||
|
}
|
||||||
|
|
||||||
/*private suspend fun buildPolylines(route: PtvRoute) {
|
/*private suspend fun buildPolylines(route: PtvRoute) {
|
||||||
val routeWithGeo = if (route.geopath.isEmpty())
|
val routeWithGeo = if (route.geopath.isEmpty())
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue