feat(server): better support for parent stops
- add datafixer to add parent stops for likely candidates
- this is mainly for bus hubs, the heuristic is the existence of a
platform code and missing parent
- use parent stops as default in route_stops
- support parent stops for stoptime querying
This commit is contained in:
parent
58649b6171
commit
c55e3a3232
11 changed files with 616 additions and 13 deletions
|
|
@ -54,6 +54,13 @@ fun Application.module() {
|
||||||
}
|
}
|
||||||
|
|
||||||
routing {
|
routing {
|
||||||
|
if (Constants.devMode) {
|
||||||
|
get("/fixup") {
|
||||||
|
call.respondText("received")
|
||||||
|
val fixer by inject<GtfsDataFixer>()
|
||||||
|
fixer.addParentsToStops()
|
||||||
|
}
|
||||||
|
}
|
||||||
get("/update") {
|
get("/update") {
|
||||||
val key = call.parameters["key"]
|
val key = call.parameters["key"]
|
||||||
if (key != Constants.updateKey) {
|
if (key != Constants.updateKey) {
|
||||||
|
|
@ -66,8 +73,11 @@ fun Application.module() {
|
||||||
?: "https://opendata.transport.vic.gov.au/dataset/3f4e292e-7f8a-4ffe-831f-1953be0fe448/resource/${datasetUuid}/download/gtfs.zip"
|
?: "https://opendata.transport.vic.gov.au/dataset/3f4e292e-7f8a-4ffe-831f-1953be0fe448/resource/${datasetUuid}/download/gtfs.zip"
|
||||||
call.respondText("received")
|
call.respondText("received")
|
||||||
launch(context = Dispatchers.IO) {
|
launch(context = Dispatchers.IO) {
|
||||||
|
val fixer by inject<GtfsDataFixer>()
|
||||||
val importer by inject<GtfsImporter>()
|
val importer by inject<GtfsImporter>()
|
||||||
importer.import(datasetUrl)
|
importer.import(datasetUrl)
|
||||||
|
|
||||||
|
fixer.addParentsToStops()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -123,7 +133,7 @@ fun Application.module() {
|
||||||
}
|
}
|
||||||
get("/route_stops/{route_id}") {
|
get("/route_stops/{route_id}") {
|
||||||
val routeId = call.parameters["route_id"]!!
|
val routeId = call.parameters["route_id"]!!
|
||||||
val useParent = call.queryParameters["parent"] in listOf("true", "1")
|
val useParent = call.queryParameters["parent"] !in listOf("false", "0")
|
||||||
val stops = withContext(Dispatchers.IO) {
|
val stops = withContext(Dispatchers.IO) {
|
||||||
val routeDao by inject<RouteDao>()
|
val routeDao by inject<RouteDao>()
|
||||||
if (useParent)
|
if (useParent)
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,43 @@
|
||||||
|
package moe.lava.banksia.server
|
||||||
|
|
||||||
|
import moe.lava.banksia.room.Database
|
||||||
|
import moe.lava.banksia.room.entity.StopEntity
|
||||||
|
import moe.lava.banksia.util.log
|
||||||
|
import java.security.MessageDigest
|
||||||
|
|
||||||
|
class GtfsDataFixer(
|
||||||
|
private val database: Database,
|
||||||
|
) {
|
||||||
|
suspend fun addParentsToStops() {
|
||||||
|
val dao = database.stopDao
|
||||||
|
val stops = dao.getAllParentless()
|
||||||
|
stops
|
||||||
|
.groupBy { it.name.split("/")[0] }
|
||||||
|
.filter { (_, stops) -> stops.size > 1 }
|
||||||
|
.forEach { (name, stops) ->
|
||||||
|
val avgLat = stops.map { it.lat }.average()
|
||||||
|
val avgLng = stops.map { it.lng }.average()
|
||||||
|
val hash = name.sha256().substring(0, 7)
|
||||||
|
val parentId = "bsia:df1:$hash"
|
||||||
|
val parent = StopEntity(
|
||||||
|
id = parentId,
|
||||||
|
name = name,
|
||||||
|
lat = avgLat,
|
||||||
|
lng = avgLng,
|
||||||
|
parent = "",
|
||||||
|
hasWheelChairBoarding = stops.all { it.hasWheelChairBoarding },
|
||||||
|
level = "",
|
||||||
|
platformCode = "",
|
||||||
|
)
|
||||||
|
log("datafixer", "inserting ${parentId} for ${stops.size} children")
|
||||||
|
dao.insertAll(parent)
|
||||||
|
database.stopDao.updateParents(stops.map { it.id }, parentId)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun String.sha256() =
|
||||||
|
MessageDigest
|
||||||
|
.getInstance("SHA-256")
|
||||||
|
.digest(this.toByteArray())
|
||||||
|
.joinToString("") { "%02x".format(it) }
|
||||||
|
|
@ -1,6 +1,7 @@
|
||||||
package moe.lava.banksia.server.di
|
package moe.lava.banksia.server.di
|
||||||
|
|
||||||
import io.ktor.client.HttpClient
|
import io.ktor.client.HttpClient
|
||||||
|
import moe.lava.banksia.server.GtfsDataFixer
|
||||||
import moe.lava.banksia.server.GtfsImporter
|
import moe.lava.banksia.server.GtfsImporter
|
||||||
import moe.lava.banksia.server.gtfs.GtfsParser
|
import moe.lava.banksia.server.gtfs.GtfsParser
|
||||||
import moe.lava.banksia.server.gtfsrt.GtfsrtService
|
import moe.lava.banksia.server.gtfsrt.GtfsrtService
|
||||||
|
|
@ -12,5 +13,6 @@ val ServerModules = module {
|
||||||
singleOf(::GtfsParser)
|
singleOf(::GtfsParser)
|
||||||
singleOf(::GtfsrtService)
|
singleOf(::GtfsrtService)
|
||||||
|
|
||||||
|
singleOf(::GtfsDataFixer)
|
||||||
singleOf(::GtfsImporter)
|
singleOf(::GtfsImporter)
|
||||||
}
|
}
|
||||||
|
|
|
||||||
498
shared/schemas/moe.lava.banksia.room.Database/11.json
Normal file
498
shared/schemas/moe.lava.banksia.room.Database/11.json
Normal file
|
|
@ -0,0 +1,498 @@
|
||||||
|
{
|
||||||
|
"formatVersion": 1,
|
||||||
|
"database": {
|
||||||
|
"version": 11,
|
||||||
|
"identityHash": "c4be3d0c2a25f8c5c33132646a070d0e",
|
||||||
|
"entities": [
|
||||||
|
{
|
||||||
|
"tableName": "Route",
|
||||||
|
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` TEXT NOT NULL, `type` INTEGER NOT NULL, `number` TEXT, `name` TEXT NOT NULL, PRIMARY KEY(`id`))",
|
||||||
|
"fields": [
|
||||||
|
{
|
||||||
|
"fieldPath": "id",
|
||||||
|
"columnName": "id",
|
||||||
|
"affinity": "TEXT",
|
||||||
|
"notNull": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "type",
|
||||||
|
"columnName": "type",
|
||||||
|
"affinity": "INTEGER",
|
||||||
|
"notNull": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "number",
|
||||||
|
"columnName": "number",
|
||||||
|
"affinity": "TEXT"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "name",
|
||||||
|
"columnName": "name",
|
||||||
|
"affinity": "TEXT",
|
||||||
|
"notNull": true
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"primaryKey": {
|
||||||
|
"autoGenerate": false,
|
||||||
|
"columnNames": [
|
||||||
|
"id"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"tableName": "Service",
|
||||||
|
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` TEXT NOT NULL, `days` INTEGER NOT NULL, `start` INTEGER NOT NULL, `end` INTEGER NOT NULL, PRIMARY KEY(`id`))",
|
||||||
|
"fields": [
|
||||||
|
{
|
||||||
|
"fieldPath": "id",
|
||||||
|
"columnName": "id",
|
||||||
|
"affinity": "TEXT",
|
||||||
|
"notNull": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "days",
|
||||||
|
"columnName": "days",
|
||||||
|
"affinity": "INTEGER",
|
||||||
|
"notNull": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "start",
|
||||||
|
"columnName": "start",
|
||||||
|
"affinity": "INTEGER",
|
||||||
|
"notNull": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "end",
|
||||||
|
"columnName": "end",
|
||||||
|
"affinity": "INTEGER",
|
||||||
|
"notNull": true
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"primaryKey": {
|
||||||
|
"autoGenerate": false,
|
||||||
|
"columnNames": [
|
||||||
|
"id"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"indices": [
|
||||||
|
{
|
||||||
|
"name": "index_Service_days",
|
||||||
|
"unique": false,
|
||||||
|
"columnNames": [
|
||||||
|
"days"
|
||||||
|
],
|
||||||
|
"orders": [],
|
||||||
|
"createSql": "CREATE INDEX IF NOT EXISTS `index_Service_days` ON `${TABLE_NAME}` (`days`)"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"tableName": "ServiceException",
|
||||||
|
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`serviceId` TEXT NOT NULL, `date` INTEGER NOT NULL, `type` INTEGER NOT NULL, PRIMARY KEY(`serviceId`, `date`))",
|
||||||
|
"fields": [
|
||||||
|
{
|
||||||
|
"fieldPath": "serviceId",
|
||||||
|
"columnName": "serviceId",
|
||||||
|
"affinity": "TEXT",
|
||||||
|
"notNull": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "date",
|
||||||
|
"columnName": "date",
|
||||||
|
"affinity": "INTEGER",
|
||||||
|
"notNull": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "type",
|
||||||
|
"columnName": "type",
|
||||||
|
"affinity": "INTEGER",
|
||||||
|
"notNull": true
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"primaryKey": {
|
||||||
|
"autoGenerate": false,
|
||||||
|
"columnNames": [
|
||||||
|
"serviceId",
|
||||||
|
"date"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"indices": [
|
||||||
|
{
|
||||||
|
"name": "index_ServiceException_serviceId",
|
||||||
|
"unique": false,
|
||||||
|
"columnNames": [
|
||||||
|
"serviceId"
|
||||||
|
],
|
||||||
|
"orders": [],
|
||||||
|
"createSql": "CREATE INDEX IF NOT EXISTS `index_ServiceException_serviceId` ON `${TABLE_NAME}` (`serviceId`)"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "index_ServiceException_type",
|
||||||
|
"unique": false,
|
||||||
|
"columnNames": [
|
||||||
|
"type"
|
||||||
|
],
|
||||||
|
"orders": [],
|
||||||
|
"createSql": "CREATE INDEX IF NOT EXISTS `index_ServiceException_type` ON `${TABLE_NAME}` (`type`)"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"tableName": "Shape",
|
||||||
|
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` TEXT NOT NULL, `path` BLOB NOT NULL, PRIMARY KEY(`id`))",
|
||||||
|
"fields": [
|
||||||
|
{
|
||||||
|
"fieldPath": "id",
|
||||||
|
"columnName": "id",
|
||||||
|
"affinity": "TEXT",
|
||||||
|
"notNull": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "path",
|
||||||
|
"columnName": "path",
|
||||||
|
"affinity": "BLOB",
|
||||||
|
"notNull": true
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"primaryKey": {
|
||||||
|
"autoGenerate": false,
|
||||||
|
"columnNames": [
|
||||||
|
"id"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"tableName": "Stop",
|
||||||
|
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` TEXT NOT NULL, `name` TEXT NOT NULL, `lat` REAL NOT NULL, `lng` REAL NOT NULL, `parent` TEXT, `hasWheelChairBoarding` INTEGER NOT NULL, `level` TEXT NOT NULL, `platformCode` TEXT NOT NULL, PRIMARY KEY(`id`), FOREIGN KEY(`parent`) REFERENCES `Stop`(`id`) ON UPDATE NO ACTION ON DELETE SET NULL DEFERRABLE INITIALLY DEFERRED)",
|
||||||
|
"fields": [
|
||||||
|
{
|
||||||
|
"fieldPath": "id",
|
||||||
|
"columnName": "id",
|
||||||
|
"affinity": "TEXT",
|
||||||
|
"notNull": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "name",
|
||||||
|
"columnName": "name",
|
||||||
|
"affinity": "TEXT",
|
||||||
|
"notNull": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "lat",
|
||||||
|
"columnName": "lat",
|
||||||
|
"affinity": "REAL",
|
||||||
|
"notNull": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "lng",
|
||||||
|
"columnName": "lng",
|
||||||
|
"affinity": "REAL",
|
||||||
|
"notNull": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "parent",
|
||||||
|
"columnName": "parent",
|
||||||
|
"affinity": "TEXT"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "hasWheelChairBoarding",
|
||||||
|
"columnName": "hasWheelChairBoarding",
|
||||||
|
"affinity": "INTEGER",
|
||||||
|
"notNull": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "level",
|
||||||
|
"columnName": "level",
|
||||||
|
"affinity": "TEXT",
|
||||||
|
"notNull": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "platformCode",
|
||||||
|
"columnName": "platformCode",
|
||||||
|
"affinity": "TEXT",
|
||||||
|
"notNull": true
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"primaryKey": {
|
||||||
|
"autoGenerate": false,
|
||||||
|
"columnNames": [
|
||||||
|
"id"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"indices": [
|
||||||
|
{
|
||||||
|
"name": "index_Stop_parent",
|
||||||
|
"unique": false,
|
||||||
|
"columnNames": [
|
||||||
|
"parent"
|
||||||
|
],
|
||||||
|
"orders": [],
|
||||||
|
"createSql": "CREATE INDEX IF NOT EXISTS `index_Stop_parent` ON `${TABLE_NAME}` (`parent`)"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"foreignKeys": [
|
||||||
|
{
|
||||||
|
"table": "Stop",
|
||||||
|
"onDelete": "SET NULL",
|
||||||
|
"onUpdate": "NO ACTION",
|
||||||
|
"columns": [
|
||||||
|
"parent"
|
||||||
|
],
|
||||||
|
"referencedColumns": [
|
||||||
|
"id"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"tableName": "StopTime",
|
||||||
|
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`tripId` TEXT NOT NULL, `stopId` TEXT NOT NULL, `arrivalTime` INTEGER NOT NULL, `departureTime` INTEGER NOT NULL, `headsign` TEXT, `pickupType` INTEGER NOT NULL, `dropOffType` INTEGER NOT NULL, PRIMARY KEY(`tripId`, `stopId`), FOREIGN KEY(`tripId`) REFERENCES `Trip`(`id`) ON UPDATE NO ACTION ON DELETE CASCADE , FOREIGN KEY(`stopId`) REFERENCES `Stop`(`id`) ON UPDATE NO ACTION ON DELETE CASCADE )",
|
||||||
|
"fields": [
|
||||||
|
{
|
||||||
|
"fieldPath": "tripId",
|
||||||
|
"columnName": "tripId",
|
||||||
|
"affinity": "TEXT",
|
||||||
|
"notNull": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "stopId",
|
||||||
|
"columnName": "stopId",
|
||||||
|
"affinity": "TEXT",
|
||||||
|
"notNull": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "arrivalTime",
|
||||||
|
"columnName": "arrivalTime",
|
||||||
|
"affinity": "INTEGER",
|
||||||
|
"notNull": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "departureTime",
|
||||||
|
"columnName": "departureTime",
|
||||||
|
"affinity": "INTEGER",
|
||||||
|
"notNull": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "headsign",
|
||||||
|
"columnName": "headsign",
|
||||||
|
"affinity": "TEXT"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "pickupType",
|
||||||
|
"columnName": "pickupType",
|
||||||
|
"affinity": "INTEGER",
|
||||||
|
"notNull": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "dropOffType",
|
||||||
|
"columnName": "dropOffType",
|
||||||
|
"affinity": "INTEGER",
|
||||||
|
"notNull": true
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"primaryKey": {
|
||||||
|
"autoGenerate": false,
|
||||||
|
"columnNames": [
|
||||||
|
"tripId",
|
||||||
|
"stopId"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"indices": [
|
||||||
|
{
|
||||||
|
"name": "index_StopTime_tripId",
|
||||||
|
"unique": false,
|
||||||
|
"columnNames": [
|
||||||
|
"tripId"
|
||||||
|
],
|
||||||
|
"orders": [],
|
||||||
|
"createSql": "CREATE INDEX IF NOT EXISTS `index_StopTime_tripId` ON `${TABLE_NAME}` (`tripId`)"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "index_StopTime_stopId",
|
||||||
|
"unique": false,
|
||||||
|
"columnNames": [
|
||||||
|
"stopId"
|
||||||
|
],
|
||||||
|
"orders": [],
|
||||||
|
"createSql": "CREATE INDEX IF NOT EXISTS `index_StopTime_stopId` ON `${TABLE_NAME}` (`stopId`)"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"foreignKeys": [
|
||||||
|
{
|
||||||
|
"table": "Trip",
|
||||||
|
"onDelete": "CASCADE",
|
||||||
|
"onUpdate": "NO ACTION",
|
||||||
|
"columns": [
|
||||||
|
"tripId"
|
||||||
|
],
|
||||||
|
"referencedColumns": [
|
||||||
|
"id"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"table": "Stop",
|
||||||
|
"onDelete": "CASCADE",
|
||||||
|
"onUpdate": "NO ACTION",
|
||||||
|
"columns": [
|
||||||
|
"stopId"
|
||||||
|
],
|
||||||
|
"referencedColumns": [
|
||||||
|
"id"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"tableName": "Trip",
|
||||||
|
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` TEXT NOT NULL, `routeId` TEXT NOT NULL, `serviceId` TEXT NOT NULL, `shapeId` TEXT, `tripHeadsign` TEXT NOT NULL, `directionId` TEXT NOT NULL, `blockId` TEXT NOT NULL, `wheelchairAccessible` TEXT NOT NULL, PRIMARY KEY(`id`), FOREIGN KEY(`routeId`) REFERENCES `Route`(`id`) ON UPDATE NO ACTION ON DELETE CASCADE , FOREIGN KEY(`serviceId`) REFERENCES `Service`(`id`) ON UPDATE NO ACTION ON DELETE CASCADE , FOREIGN KEY(`shapeId`) REFERENCES `Shape`(`id`) ON UPDATE NO ACTION ON DELETE CASCADE )",
|
||||||
|
"fields": [
|
||||||
|
{
|
||||||
|
"fieldPath": "id",
|
||||||
|
"columnName": "id",
|
||||||
|
"affinity": "TEXT",
|
||||||
|
"notNull": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "routeId",
|
||||||
|
"columnName": "routeId",
|
||||||
|
"affinity": "TEXT",
|
||||||
|
"notNull": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "serviceId",
|
||||||
|
"columnName": "serviceId",
|
||||||
|
"affinity": "TEXT",
|
||||||
|
"notNull": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "shapeId",
|
||||||
|
"columnName": "shapeId",
|
||||||
|
"affinity": "TEXT"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "tripHeadsign",
|
||||||
|
"columnName": "tripHeadsign",
|
||||||
|
"affinity": "TEXT",
|
||||||
|
"notNull": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "directionId",
|
||||||
|
"columnName": "directionId",
|
||||||
|
"affinity": "TEXT",
|
||||||
|
"notNull": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "blockId",
|
||||||
|
"columnName": "blockId",
|
||||||
|
"affinity": "TEXT",
|
||||||
|
"notNull": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "wheelchairAccessible",
|
||||||
|
"columnName": "wheelchairAccessible",
|
||||||
|
"affinity": "TEXT",
|
||||||
|
"notNull": true
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"primaryKey": {
|
||||||
|
"autoGenerate": false,
|
||||||
|
"columnNames": [
|
||||||
|
"id"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"indices": [
|
||||||
|
{
|
||||||
|
"name": "index_Trip_shapeId",
|
||||||
|
"unique": false,
|
||||||
|
"columnNames": [
|
||||||
|
"shapeId"
|
||||||
|
],
|
||||||
|
"orders": [],
|
||||||
|
"createSql": "CREATE INDEX IF NOT EXISTS `index_Trip_shapeId` ON `${TABLE_NAME}` (`shapeId`)"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "index_Trip_serviceId",
|
||||||
|
"unique": false,
|
||||||
|
"columnNames": [
|
||||||
|
"serviceId"
|
||||||
|
],
|
||||||
|
"orders": [],
|
||||||
|
"createSql": "CREATE INDEX IF NOT EXISTS `index_Trip_serviceId` ON `${TABLE_NAME}` (`serviceId`)"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "index_Trip_routeId",
|
||||||
|
"unique": false,
|
||||||
|
"columnNames": [
|
||||||
|
"routeId"
|
||||||
|
],
|
||||||
|
"orders": [],
|
||||||
|
"createSql": "CREATE INDEX IF NOT EXISTS `index_Trip_routeId` ON `${TABLE_NAME}` (`routeId`)"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"foreignKeys": [
|
||||||
|
{
|
||||||
|
"table": "Route",
|
||||||
|
"onDelete": "CASCADE",
|
||||||
|
"onUpdate": "NO ACTION",
|
||||||
|
"columns": [
|
||||||
|
"routeId"
|
||||||
|
],
|
||||||
|
"referencedColumns": [
|
||||||
|
"id"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"table": "Service",
|
||||||
|
"onDelete": "CASCADE",
|
||||||
|
"onUpdate": "NO ACTION",
|
||||||
|
"columns": [
|
||||||
|
"serviceId"
|
||||||
|
],
|
||||||
|
"referencedColumns": [
|
||||||
|
"id"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"table": "Shape",
|
||||||
|
"onDelete": "CASCADE",
|
||||||
|
"onUpdate": "NO ACTION",
|
||||||
|
"columns": [
|
||||||
|
"shapeId"
|
||||||
|
],
|
||||||
|
"referencedColumns": [
|
||||||
|
"id"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"tableName": "VersionMetadata",
|
||||||
|
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`type` TEXT NOT NULL, `lastUpdated` INTEGER NOT NULL, PRIMARY KEY(`type`))",
|
||||||
|
"fields": [
|
||||||
|
{
|
||||||
|
"fieldPath": "type",
|
||||||
|
"columnName": "type",
|
||||||
|
"affinity": "TEXT",
|
||||||
|
"notNull": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "lastUpdated",
|
||||||
|
"columnName": "lastUpdated",
|
||||||
|
"affinity": "INTEGER",
|
||||||
|
"notNull": true
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"primaryKey": {
|
||||||
|
"autoGenerate": false,
|
||||||
|
"columnNames": [
|
||||||
|
"type"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"setupQueries": [
|
||||||
|
"CREATE TABLE IF NOT EXISTS room_master_table (id INTEGER PRIMARY KEY,identity_hash TEXT)",
|
||||||
|
"INSERT OR REPLACE INTO room_master_table (id,identity_hash) VALUES(42, 'c4be3d0c2a25f8c5c33132646a070d0e')"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -8,7 +8,7 @@ data class Stop(
|
||||||
val id: String,
|
val id: String,
|
||||||
val name: String,
|
val name: String,
|
||||||
val pos: Point,
|
val pos: Point,
|
||||||
val parent: String,
|
val parent: String?,
|
||||||
val hasWheelChairBoarding: Boolean,
|
val hasWheelChairBoarding: Boolean,
|
||||||
val level: String,
|
val level: String,
|
||||||
val platformCode: String,
|
val platformCode: String,
|
||||||
|
|
|
||||||
|
|
@ -3,7 +3,11 @@ package moe.lava.banksia.room
|
||||||
import androidx.room.AutoMigration
|
import androidx.room.AutoMigration
|
||||||
import androidx.room.RoomDatabase
|
import androidx.room.RoomDatabase
|
||||||
import androidx.room.TypeConverters
|
import androidx.room.TypeConverters
|
||||||
|
import androidx.room.migration.Migration
|
||||||
|
import androidx.room.util.foreignKeyCheck
|
||||||
|
import androidx.sqlite.SQLiteConnection
|
||||||
import androidx.sqlite.driver.bundled.BundledSQLiteDriver
|
import androidx.sqlite.driver.bundled.BundledSQLiteDriver
|
||||||
|
import androidx.sqlite.execSQL
|
||||||
import kotlinx.coroutines.Dispatchers
|
import kotlinx.coroutines.Dispatchers
|
||||||
import kotlinx.coroutines.IO
|
import kotlinx.coroutines.IO
|
||||||
import moe.lava.banksia.room.converter.RouteTypeConverter
|
import moe.lava.banksia.room.converter.RouteTypeConverter
|
||||||
|
|
@ -26,7 +30,7 @@ import moe.lava.banksia.room.entity.VersionMetadataEntity
|
||||||
import androidx.room.Database as DatabaseAnnotation
|
import androidx.room.Database as DatabaseAnnotation
|
||||||
|
|
||||||
@DatabaseAnnotation(
|
@DatabaseAnnotation(
|
||||||
version = 10,
|
version = 11,
|
||||||
entities = [
|
entities = [
|
||||||
RouteEntity::class,
|
RouteEntity::class,
|
||||||
ServiceEntity::class,
|
ServiceEntity::class,
|
||||||
|
|
@ -59,7 +63,21 @@ abstract class Database : RoomDatabase() {
|
||||||
base.fallbackToDestructiveMigration(true)
|
base.fallbackToDestructiveMigration(true)
|
||||||
.setDriver(BundledSQLiteDriver())
|
.setDriver(BundledSQLiteDriver())
|
||||||
.setQueryCoroutineContext(Dispatchers.IO)
|
.setQueryCoroutineContext(Dispatchers.IO)
|
||||||
|
.addMigrations(MIGRATION_10_11)
|
||||||
// .fallbackToDestructiveMigration(true)
|
// .fallbackToDestructiveMigration(true)
|
||||||
.build()
|
.build()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
val MIGRATION_10_11 = object : Migration(10, 11) {
|
||||||
|
override fun migrate(connection: SQLiteConnection) {
|
||||||
|
connection.execSQL("CREATE TABLE IF NOT EXISTS `_new_Stop` (`id` TEXT NOT NULL, `name` TEXT NOT NULL, `lat` REAL NOT NULL, `lng` REAL NOT NULL, `parent` TEXT, `hasWheelChairBoarding` INTEGER NOT NULL, `level` TEXT NOT NULL, `platformCode` TEXT NOT NULL, PRIMARY KEY(`id`), FOREIGN KEY(`parent`) REFERENCES `Stop`(`id`) ON UPDATE NO ACTION ON DELETE SET NULL DEFERRABLE INITIALLY DEFERRED)")
|
||||||
|
connection.execSQL("INSERT INTO `_new_Stop` (`id`,`name`,`lat`,`lng`,`parent`,`hasWheelChairBoarding`,`level`,`platformCode`) SELECT `id`,`name`,`lat`,`lng`,`parent`,`hasWheelChairBoarding`,`level`,`platformCode` FROM `Stop`")
|
||||||
|
connection.execSQL("UPDATE `_new_Stop` SET `parent` = NULL WHERE `parent` == \"\"")
|
||||||
|
connection.execSQL("DROP TABLE `Stop`")
|
||||||
|
connection.execSQL("ALTER TABLE `_new_Stop` RENAME TO `Stop`")
|
||||||
|
connection.execSQL("CREATE INDEX IF NOT EXISTS `index_Stop_parent` ON `Stop` (`parent`)")
|
||||||
|
connection.execSQL("CREATE INDEX IF NOT EXISTS `index_Trip_serviceId` ON `Trip` (`serviceId`)")
|
||||||
|
foreignKeyCheck(connection, "Stop")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -37,13 +37,22 @@ interface RouteDao {
|
||||||
""")
|
""")
|
||||||
suspend fun stops(id: String): List<StopEntity>
|
suspend fun stops(id: String): List<StopEntity>
|
||||||
|
|
||||||
|
// I vibecoded this, sorry
|
||||||
@Query("""
|
@Query("""
|
||||||
SELECT Stop.* FROM Stop
|
WITH Tree AS (
|
||||||
INNER JOIN Stop Child ON Child.parent == Stop.id
|
SELECT Stop.* FROM Stop
|
||||||
INNER JOIN StopTime ON StopTime.stopId == Child.id
|
INNER JOIN StopTime ON StopTime.stopId == Stop.id
|
||||||
INNER JOIN Trip ON Trip.id == StopTime.tripId
|
INNER JOIN Trip ON Trip.id == StopTime.tripId
|
||||||
WHERE Trip.routeId == :id
|
WHERE Trip.routeId == :id
|
||||||
GROUP BY Stop.id
|
GROUP BY Stop.id
|
||||||
|
|
||||||
|
UNION ALL
|
||||||
|
|
||||||
|
SELECT s.*
|
||||||
|
FROM Stop s
|
||||||
|
INNER JOIN Tree t ON s.id = t.parent
|
||||||
|
)
|
||||||
|
SELECT DISTINCT * FROM Tree WHERE parent IS NULL;
|
||||||
""")
|
""")
|
||||||
suspend fun stopsParent(id: String): List<StopEntity>
|
suspend fun stopsParent(id: String): List<StopEntity>
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -12,6 +12,13 @@ interface StopDao {
|
||||||
@Query("SELECT * FROM Stop")
|
@Query("SELECT * FROM Stop")
|
||||||
suspend fun getAll(): List<StopEntity>
|
suspend fun getAll(): List<StopEntity>
|
||||||
|
|
||||||
|
@Query("""
|
||||||
|
SELECT * FROM Stop
|
||||||
|
WHERE platformCode <> ""
|
||||||
|
AND parent == ""
|
||||||
|
""")
|
||||||
|
suspend fun getAllParentless(): List<StopEntity>
|
||||||
|
|
||||||
@Query("SELECT * FROM Stop WHERE id == :id")
|
@Query("SELECT * FROM Stop WHERE id == :id")
|
||||||
suspend fun get(id: String): StopEntity?
|
suspend fun get(id: String): StopEntity?
|
||||||
|
|
||||||
|
|
@ -29,4 +36,7 @@ interface StopDao {
|
||||||
|
|
||||||
@Query("DELETE FROM Stop")
|
@Query("DELETE FROM Stop")
|
||||||
suspend fun deleteAll()
|
suspend fun deleteAll()
|
||||||
|
|
||||||
|
@Query("UPDATE Stop SET parent = :parent WHERE id IN (:ids)")
|
||||||
|
suspend fun updateParents(ids: List<String>, parent: String)
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -27,7 +27,7 @@ interface StopTimeDao {
|
||||||
INNER JOIN Trip ON Trip.serviceId == Service.id
|
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
|
WHERE StopTime.tripId == Trip.id
|
||||||
AND StopTime.stopId == :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
|
||||||
""")
|
""")
|
||||||
suspend fun getForStopDated(stopId: String, days: Int, date: Int): List<StopTimeEntity>
|
suspend fun getForStopDated(stopId: String, days: Int, date: Int): List<StopTimeEntity>
|
||||||
|
|
|
||||||
|
|
@ -2,17 +2,30 @@ package moe.lava.banksia.room.entity
|
||||||
|
|
||||||
import androidx.room.ColumnInfo
|
import androidx.room.ColumnInfo
|
||||||
import androidx.room.Entity
|
import androidx.room.Entity
|
||||||
|
import androidx.room.ForeignKey
|
||||||
|
import androidx.room.ForeignKey.Companion.SET_NULL
|
||||||
import androidx.room.PrimaryKey
|
import androidx.room.PrimaryKey
|
||||||
import moe.lava.banksia.model.Stop
|
import moe.lava.banksia.model.Stop
|
||||||
import moe.lava.banksia.util.Point
|
import moe.lava.banksia.util.Point
|
||||||
|
|
||||||
@Entity("Stop")
|
@Entity(
|
||||||
|
"Stop",
|
||||||
|
foreignKeys = [
|
||||||
|
ForeignKey(
|
||||||
|
StopEntity::class,
|
||||||
|
parentColumns = ["id"],
|
||||||
|
childColumns = ["parent"],
|
||||||
|
onDelete = SET_NULL,
|
||||||
|
deferred = true,
|
||||||
|
),
|
||||||
|
]
|
||||||
|
)
|
||||||
data class StopEntity(
|
data class StopEntity(
|
||||||
@PrimaryKey val id: String,
|
@PrimaryKey val id: String,
|
||||||
val name: String,
|
val name: String,
|
||||||
val lat: Double,
|
val lat: Double,
|
||||||
val lng: Double,
|
val lng: Double,
|
||||||
@ColumnInfo(index = true) val parent: String,
|
@ColumnInfo(index = true) val parent: String?,
|
||||||
val hasWheelChairBoarding: Boolean,
|
val hasWheelChairBoarding: Boolean,
|
||||||
val level: String,
|
val level: String,
|
||||||
val platformCode: String,
|
val platformCode: String,
|
||||||
|
|
|
||||||
|
|
@ -15,7 +15,7 @@ import moe.lava.banksia.model.Trip
|
||||||
ForeignKey(ServiceEntity::class, parentColumns = ["id"], childColumns = ["serviceId"], onDelete = CASCADE),
|
ForeignKey(ServiceEntity::class, parentColumns = ["id"], childColumns = ["serviceId"], onDelete = CASCADE),
|
||||||
ForeignKey(ShapeEntity::class, parentColumns = ["id"], childColumns = ["shapeId"], onDelete = CASCADE),
|
ForeignKey(ShapeEntity::class, parentColumns = ["id"], childColumns = ["shapeId"], onDelete = CASCADE),
|
||||||
],
|
],
|
||||||
indices = [Index("shapeId")],
|
indices = [Index("shapeId"), Index("serviceId")],
|
||||||
)
|
)
|
||||||
data class TripEntity(
|
data class TripEntity(
|
||||||
@PrimaryKey val id: String,
|
@PrimaryKey val id: String,
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue