Files
oeffimaster/lib/transport_helper.dart
T

366 lines
9.1 KiB
Dart

import 'package:sqflite/sqflite.dart';
// import './state_helper.dart';
// import './db_helper.dart';
// TODO: get access to DB
class DbHelper {
static late Database db;
static String dbName = "";
// DbHelper({
// required this.db,
// required this.dbName,
// });
// remnant from when DB was not static
// factory DbHelper._openDb(Database database, String dbName) => DbHelper(
// db: database,
// dbName: dbName,
// );
static void initDb(String dbName) async {
// var db = await openDatabase('/var/db/oeffimaster/transport_db.db');
var databasesPath = await getDatabasesPath();
var path = databasesPath + dbName;
// var newDb = await openDatabase(path);
// var dbHelper = DbHelper._openDb(newDb, dbName);
db = await openDatabase(path);
dbName = dbName;
// TODO: Make sure the directory exists
// TODO: future optimisation: batch transactions
Route.initRouteTable();
Segment.initSegmentTable();
Station.initStationTable();
Vehicle.initVehicleTable();
Departure.initDepartureTable();
Route.updateHighestId();
}
}
class Route {
static int highestId = 0;
static const String tableName = "route";
int id;
List<Segment> segments = List.empty();
Duration duration = Duration(minutes: 0);
Route({
required this.id,
required this.segments,
required this.duration,
});
// from trip API request
// TODO: how to connect this best with the database for read/write?
factory Route.fromJson(Map<String, dynamic> json) {
Route newRoute = Route(
id: highestId,
segments: json["LegList"]["Leg"].map((leg) => Segment.fromJson(leg)),
duration:
json["LegList"]["Leg"][-1]["Origin"]["time"] -
json["LegList"]["Leg"][0]["Origin"]["time"],
);
highestId += 1;
return newRoute;
}
// rowids will give us new ids for every route-segment connection, but routeid
// is needed as well, so how do we create these - query highest existing routeid? I think so
// TODO: get highest routeid in route table (fast)
static void initRouteTable() async {
Database database = DbHelper.db;
database.execute("""CREATE TABLE IF NOT EXISTS routesegments(
id TEXT,
segmentid INTEGER,
segmentorder INTEGER, -- TODO: implement sorting
FOREIGN KEY(segmentid) REFERENCES segment(id)
);
""");
}
static void updateHighestId() async {
Database database = DbHelper.db;
int newHigh = await getHighestId();
highestId = newHigh + 1;
}
/* TODO: better separation between production db and test db
* they share the highestId field regardless, which wont do
* so is an attribut for database needed?
*/
/// parse the highest id from the database to continue with ongoing ids but
/// create the db entry after the class, for now
// -- this might be making it more complicated than it needs to be
static Future<int> getHighestId() async {
Database database = DbHelper.db;
try {
var tmp = await database.query(
"routesegments",
columns: ["id"],
distinct: true,
orderBy: "id",
whereArgs: ["id"],
limit: 1,
);
if (tmp.isEmpty) {
return 0;
}
if (tmp[0].isEmpty) {
return 0;
}
// entry "routeid" should definitely exist
assert(tmp[0]["id"] != null);
// Source - https://stackoverflow.com/a/52632172
// Posted by Günter Zöchbauer, modified by community. See post 'Timeline' for change history
// Retrieved 2026-06-08, License - CC BY-SA 4.0
T? cast<T>(x) => x is T ? x : null;
// --
//doing this in two steps go deal with nullability
String highestId = cast<String>(tmp[0]["routeid"]) ?? "0";
return int.parse(highestId);
} catch (e) {
print('Error: Could not get id: $e'); // TODO: use logging
}
return 0;
}
void writeNewToTable(Route route) async {
Database database = DbHelper.db;
for (var i = 0; i < route.segments.length; i++) {
database.insert("routesegments", {
"id": route.id,
"segmentid": route.segments[i].id,
"segmentorder": i,
});
}
}
}
class Segment {
final int id = 0;
final Station startPoint;
final Station endPoint;
final DateTime startTime;
final DateTime endTime;
final Vehicle vehicle;
Segment({
required this.startPoint,
required this.endPoint,
required this.startTime,
required this.endTime,
required this.vehicle,
});
factory Segment.fromJson(Map<String, dynamic> json) {
Database database = DbHelper.db;
Segment tmp = Segment(
startPoint: Station.fromJson(json["Origin"]),
endPoint: Station.fromJson(json["Destination"]),
startTime: json["Origin"]["rtTime"],
endTime: json["Destination"]["rtTime"],
vehicle: Vehicle.fromJson(json["Product"]),
);
tmp.writeNewToTable(); // TODO: we need to write to db and get the ID!!!!!!!
return tmp;
}
static void initSegmentTable() async {
Database database = DbHelper.db;
database.execute("""
CREATE TABLE IF NOT EXISTS segment(
startpoint TEXT,
endpoint TEXT,
starttime TEXT,
endtime TEXT,
vehicle INTEGER,
FOREIGN KEY(startpoint) REFERENCES station(stationid),
FOREIGN KEY(endpoint) REFERENCES station(stationid),
FOREIGN KEY(vehicle) REFERENCES vehicle(vehicleid)
);
""");
}
Future<int> writeNewToTable() async {
Database database = DbHelper.db;
int id = await database.insert("segment", {
"startpoint": startPoint.id,
"endpoint": startPoint.id,
"startTime": startTime,
"endTime": endTime,
"vehicle": vehicle.id,
});
return id;
}
}
class Station {
final String id;
final String name;
List<Line>? transportLines; // could be normalized away
Station({
required this.id,
required this.name,
required this.transportLines, //TODO: make extra db table
});
factory Station.fromJson(Map<String, dynamic> json) => Station(
id: json["id"] as String? ?? json["extId"] as String,
name: json["name"],
transportLines: json.containsKey("productAtStop")
? json["productAtStop"].map(
(jsonLine) => Line.fromJson(jsonLine as Map<String, dynamic>),
)
: const [],
);
static void initStationTable() async {
Database database = DbHelper.db;
database.execute("""CREATE TABLE IF NOT EXISTS station(
id TEXT PRIMARY KEY,
name TEXT
);
""");
}
}
class Vehicle {
final String id;
final Line line;
Vehicle({
required this.id,
required this.line,
});
// we can use the "product" class from within any JSON response
// (within hafas API, on the appropriate level)
factory Vehicle.fromJson(Map<String, dynamic> json) => Vehicle(
id: json["matchId"],
line: Line.fromJson(json),
);
static void initVehicleTable() async {
Database database = DbHelper.db;
database.execute("""CREATE TABLE IF NOT EXISTS vehicle(
id TEXT PRIMARY KEY,
line TEXT,
FOREIGN KEY(line) REFERENCES line(lineid)
);
""");
}
}
class Line {
final String id;
Station? destination;
Station? direction;
final VehicleType mode;
final String name;
Color? fgColor;
Color? bgColor;
Line({
required this.id,
required this.name,
required this.mode,
this.fgColor,
this.bgColor,
});
// TODO: EXPLAIN!!!!!!!!!!!!
static const _vehicleTypeAsCatCode = [
VehicleType.metro,
VehicleType.subway,
VehicleType.PLACEHOLDER,
VehicleType.bus,
];
factory Line.fromJson(Map<String, dynamic> json) => Line(
id: json["lineId"],
name: json["name"],
mode: _vehicleTypeAsCatCode[int.parse(json["catCode"])],
fgColor:
Color.fromJson(json["foregroundColor"]) as Color? ??
Color(r: 255, g: 255, b: 255), // fallback
bgColor:
Color.fromJson(json["backgroundColor"]) as Color? ??
Color(r: 50, g: 50, b: 50), // fallback
);
static void initLineTable() async {
Database database = DbHelper.db;
database.execute("""CREATE TABLE IF NOT EXISTS line(
lineid TEXT PRIMARY KEY,
destination TEXT,
direction TEXT,
mode TEXT,
name TEXT,
fgcolor TEXT,
bgcolor TEXT
);
""");
}
}
class Departure {
String? id;
final Vehicle vehicle;
final Station station;
final DateTime departureTime;
Departure({
required this.vehicle,
required this.station,
required this.departureTime,
});
static void initDepartureTable() async {
Database database = DbHelper.db;
database.execute("""CREATE TABLE IF NOT EXISTS departure(
id INTEGER,
vehicle TEXT,
station TEXT,
departuretime TEXT,
FOREIGN KEY(vehicle) REFERENCES vehicle(id),
FOREIGN KEY(station) REFERENCES station(id)
);
""");
}
}
class Color {
final int r;
final int g;
final int b;
Color({
required this.r,
required this.g,
required this.b,
});
factory Color.fromJson(Map<String, dynamic> json) => Color(
r: json["r"],
g: json["g"],
b: json["b"],
);
}
enum VehicleType {
bus,
tram,
metro,
subway,
regionalTrain,
PLACEHOLDER,
}