feat: di, db, and preliminary server-side gtfs parsing

This commit is contained in:
LavaDesu 2025-08-08 01:59:32 +10:00
parent ccc748dc1f
commit 6770c01613
Signed by: cilly
GPG key ID: 6500251E087653C9
22 changed files with 555 additions and 24 deletions

View file

@ -2,18 +2,7 @@ package moe.lava.banksia.data.ptv.structures
import kotlinx.serialization.SerialName
import kotlinx.serialization.Serializable
// Ordinals used for sorting in searcher
enum class GtfsSubType(val value: Int) {
MetroTrain(2),
MetroTram(3),
MetroBus(4),
RegionalTrain(1),
RegionalCoach(5),
RegionalBus(6),
SkyBus(11),
Interstate(10),
}
import moe.lava.banksia.model.RouteType
@Serializable
data class PtvRoute(
@ -24,13 +13,8 @@ data class PtvRoute(
@SerialName("route_gtfs_id") val routeGtfsId: String,
@SerialName("geopath") val geopath: List<PtvGeopath>,
) {
fun gtfsSubType(): GtfsSubType? {
GtfsSubType.entries.forEach {
if (routeGtfsId.startsWith(it.value.toString()))
return it
}
return null
}
fun gtfsSubType(): RouteType =
RouteType.entries.first { routeGtfsId.startsWith(it.value.toString() + "-") }
fun getShortFullName(): String {
var res = ""

View file

@ -0,0 +1,12 @@
package moe.lava.banksia.di
import moe.lava.banksia.room.Database
import org.koin.dsl.module
val CommonModules = module {
includes(PlatformModule)
single { Database.build(get<PlatformDatabaseBuilder>().getBuilder()) }
single { get<Database>().getRouteDao() }
single { get<Database>().getShapeDao() }
}

View file

@ -0,0 +1,17 @@
package moe.lava.banksia.di
import androidx.room.RoomDatabase
import moe.lava.banksia.room.Database
import org.koin.core.parameter.ParametersHolder
import org.koin.core.scope.Scope
import org.koin.dsl.module
interface PlatformDatabaseBuilder {
fun getBuilder(): RoomDatabase.Builder<Database>
}
expect fun Scope.provideDatabaseBuilder(p: ParametersHolder): PlatformDatabaseBuilder
internal val PlatformModule = module {
single { provideDatabaseBuilder(it) }
}

View file

@ -0,0 +1,12 @@
package moe.lava.banksia.model
import androidx.room.Entity
import androidx.room.PrimaryKey
@Entity
data class Route(
@PrimaryKey val id: String,
val type: RouteType,
val number: String?,
val name: String,
)

View file

@ -0,0 +1,23 @@
package moe.lava.banksia.model
import androidx.room.TypeConverter
enum class RouteType(val value: Int) {
MetroTrain(2),
MetroTram(3),
MetroBus(4),
RegionalTrain(1),
RegionalCoach(5),
RegionalBus(6),
SkyBus(11),
Interstate(10),
;
companion object {
@TypeConverter
fun from(value: Int) = RouteType.entries.first { it.value == value }
@TypeConverter
fun to(routeType: RouteType) = routeType.value
}
}

View file

@ -0,0 +1,16 @@
package moe.lava.banksia.model
import androidx.room.Entity
import androidx.room.PrimaryKey
import androidx.room.TypeConverters
import moe.lava.banksia.room.converter.ShapeConverter
import moe.lava.banksia.util.Point
typealias ShapePath = List<Point>
@Entity
@TypeConverters(ShapeConverter::class)
data class Shape(
@PrimaryKey val id: String,
val path: ShapePath,
)

View file

@ -0,0 +1,28 @@
package moe.lava.banksia.room
import androidx.room.RoomDatabase
import androidx.room.TypeConverters
import androidx.sqlite.driver.bundled.BundledSQLiteDriver
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.IO
import moe.lava.banksia.model.Route
import moe.lava.banksia.model.RouteType
import moe.lava.banksia.model.Shape
import moe.lava.banksia.room.dao.RouteDao
import moe.lava.banksia.room.dao.ShapeDao
import androidx.room.Database as DatabaseAnnotation
@DatabaseAnnotation(entities = [Route::class, Shape::class], version = 1)
@TypeConverters(RouteType.Companion::class)
abstract class Database : RoomDatabase() {
abstract fun getRouteDao(): RouteDao
abstract fun getShapeDao(): ShapeDao
companion object {
fun build(base: Builder<Database>) =
base.fallbackToDestructiveMigrationOnDowngrade(true)
.setDriver(BundledSQLiteDriver())
.setQueryCoroutineContext(Dispatchers.IO)
.build()
}
}

View file

@ -0,0 +1,43 @@
package moe.lava.banksia.room.converter
import androidx.room.TypeConverter
import moe.lava.banksia.model.ShapePath
import moe.lava.banksia.util.Point
object ShapeConverter {
@TypeConverter
fun from(value: ByteArray): ShapePath {
return value
.asIterable()
.chunked(8) {
(it[0].toLong() and 0xFF) or
(it[1].toLong() and 0xFF shl 8) or
(it[2].toLong() and 0xFF shl 16) or
(it[3].toLong() and 0xFF shl 24) or
(it[4].toLong() and 0xFF shl 32) or
(it[5].toLong() and 0xFF shl 40) or
(it[6].toLong() and 0xFF shl 48) or
(it[7].toLong() and 0xFF shl 56)
}
.map { Double.fromBits(it) }
.chunked(2)
.map { (lat, lng) -> Point(lat, lng) }
}
@TypeConverter
fun to(path: ShapePath): ByteArray {
return path
.flatMap { (lat, lng) -> listOf(lat.toBits(), lng.toBits()) }
.flatMap { i -> listOf(
i.toByte(),
(i shr 8).toByte(),
(i shr 16).toByte(),
(i shr 24).toByte(),
(i shr 32).toByte(),
(i shr 40).toByte(),
(i shr 48).toByte(),
(i shr 56).toByte(),
) }
.toByteArray()
}
}

View file

@ -0,0 +1,25 @@
package moe.lava.banksia.room.dao
import androidx.room.Dao
import androidx.room.Delete
import androidx.room.Insert
import androidx.room.Query
import moe.lava.banksia.model.Route
@Dao
interface RouteDao {
@Query("SELECT * FROM Route")
suspend fun getAll(): List<Route>
@Query("SELECT * FROM Route WHERE id == :id")
suspend fun get(id: String): Route?
@Insert
suspend fun insertAll(vararg routes: Route)
@Delete
suspend fun delete(route: Route)
@Query("DELETE FROM Route")
suspend fun deleteAll()
}

View file

@ -0,0 +1,22 @@
package moe.lava.banksia.room.dao
import androidx.room.Dao
import androidx.room.Delete
import androidx.room.Insert
import androidx.room.Query
import moe.lava.banksia.model.Shape
@Dao
interface ShapeDao {
@Query("SELECT * FROM Shape WHERE id == :id")
suspend fun get(id: String): Shape?
@Insert
suspend fun insertAll(vararg shapes: Shape)
@Delete
suspend fun delete(shape: Shape)
@Query("DELETE FROM Shape")
suspend fun deleteAll()
}