From 864b18346fea4480cff404a05a6baa1fefdaa84b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mathieu=20M=C3=A9a?= Date: Tue, 31 Mar 2026 14:37:37 -0400 Subject: [PATCH 01/17] GTFS-RT > Trip ID no match fallback - [x] vehicle location - [ ] alerts - [ ] trip update --- .../provider/gtfs/GTFSRealTimeProviderExt.kt | 61 ++++++++++++++++++- .../status/GTFSRealTimeTripUpdatesProvider.kt | 4 +- .../GTFSRealTimeVehiclePositionsProvider.kt | 22 ++++--- .../VehicleLocationProvider.kt | 4 ++ 4 files changed, 76 insertions(+), 15 deletions(-) diff --git a/src/main/java/org/mtransit/android/commons/provider/gtfs/GTFSRealTimeProviderExt.kt b/src/main/java/org/mtransit/android/commons/provider/gtfs/GTFSRealTimeProviderExt.kt index dcaef724..93abebdf 100644 --- a/src/main/java/org/mtransit/android/commons/provider/gtfs/GTFSRealTimeProviderExt.kt +++ b/src/main/java/org/mtransit/android/commons/provider/gtfs/GTFSRealTimeProviderExt.kt @@ -20,17 +20,21 @@ import org.mtransit.android.commons.provider.GTFSRealTimeProvider.getAgencyRoute import org.mtransit.android.commons.provider.GTFSRealTimeProvider.getAgencyRouteTypeTagTargetUUID import org.mtransit.android.commons.provider.GTFSRealTimeProvider.getAgencyStopTagTargetUUID import org.mtransit.android.commons.provider.GTFSRealTimeProvider.getAgencyTagTargetUUID +import org.mtransit.android.commons.provider.GTFSRealTimeProvider.isIGNORE_DIRECTION import org.mtransit.android.commons.provider.GTFSRealTimeProvider.isUSE_URL_HASH_SECRET_AND_DATE import org.mtransit.android.commons.provider.gtfs.GtfsRealtimeExt.optRouteId import org.mtransit.android.commons.provider.gtfs.GtfsRealtimeExt.optStopId import org.mtransit.android.commons.provider.gtfs.GtfsRealtimeExt.optTripId import org.mtransit.android.commons.provider.gtfs.GtfsRealtimeExt.originalIdToHash import org.mtransit.android.commons.provider.gtfs.GtfsRealtimeExt.originalIdToId +import org.mtransit.android.commons.provider.vehiclelocations.VehicleLocationProviderContract import java.net.URL import com.google.transit.realtime.GtfsRealtime.EntitySelector as GEntitySelector import com.google.transit.realtime.GtfsRealtime.TripDescriptor as GTripDescriptor import com.google.transit.realtime.GtfsRealtime.TripUpdate.StopTimeUpdate as GTUStopTimeUpdate +val GTFSRealTimeProvider.ignoreDirection get() = isIGNORE_DIRECTION(this.requireContextCompat()) + private val GTFSRealTimeProvider.routeIdCleanupPattern get() = getRouteIdCleanupPattern(requireContextCompat()) fun GTFSRealTimeProvider.parseRouteId(es: GEntitySelector) = es.optRouteId?.let { parseRouteId(it) } fun GTFSRealTimeProvider.parseRouteId(td: GTripDescriptor) = td.optRouteId?.let { parseRouteId(it) } @@ -42,6 +46,7 @@ fun GTFSRealTimeProvider.parseTripId(gTripId: String) = gTripId.originalIdToId(t private val GTFSRealTimeProvider.stopIdCleanupPattern get() = getStopIdCleanupPattern(requireContextCompat()) fun GTFSRealTimeProvider.parseStopId(es: GEntitySelector) = es.optStopId?.let { parseStopId(it) } +@Suppress("unused") fun GTFSRealTimeProvider.parseStopId(stu: GTUStopTimeUpdate) = stu.optStopId?.let { parseStopId(it) } fun GTFSRealTimeProvider.parseStopId(gStopId: String) = gStopId.originalIdToHash(stopIdCleanupPattern) @@ -62,6 +67,50 @@ fun RouteDirectionStop.getRouteTag(provider: GTFSRealTimeProvider) = this.route. fun RouteDirectionStop.getDirectionTag(provider: GTFSRealTimeProvider) = this.direction.getDirectionTag(provider) fun RouteDirectionStop.getStopTag(provider: GTFSRealTimeProvider) = this.stop.getStopTag(provider) +fun VehicleLocationProviderContract.Filter.getPrimaryTargetUUIDs( + provider: GTFSRealTimeProvider, + ignoreDirection: Boolean = false, + includeStopTags: Boolean = false +): Pair? = + (poi as? RouteDirectionStop)?.getPrimaryTargetUUIDs(provider, ignoreDirection, includeStopTags) + ?: routeDirection?.getPrimaryTargetUUIDs(provider, ignoreDirection) + ?: route?.getPrimaryTargetUUIDs(provider) + +fun RouteDirectionStop.getPrimaryTargetUUIDs( + provider: GTFSRealTimeProvider, + ignoreDirection: Boolean = false, + includeStopTags: Boolean = false +) = when { + includeStopTags && ignoreDirection -> getAgencyRouteStopTagTargetUUID(provider.agencyTag, getRouteTag(provider), getStopTag(provider))?.let { it to uuid } + includeStopTags -> + getAgencyRouteDirectionStopTagTargetUUID(provider.agencyTag, getRouteTag(provider), getDirectionTag(provider), getStopTag(provider))?.let { it to uuid } + + else -> getAgencyRouteDirectionTagTargetUUID(provider.agencyTag, getRouteTag(provider), getDirectionTag(provider))?.let { it to routeDirectionUUID } +} + +fun RouteDirection.getPrimaryTargetUUIDs( + provider: GTFSRealTimeProvider, + ignoreDirection: Boolean = false, +) = when { + ignoreDirection -> getAgencyRouteTagTargetUUID(provider.agencyTag, getRouteTag(provider)) to route.uuid + else -> getAgencyRouteDirectionTagTargetUUID(provider.agencyTag, getRouteTag(provider), getDirectionTag(provider))?.let { it to uuid } +} + +fun Route.getPrimaryTargetUUIDs( + provider: GTFSRealTimeProvider, +) = + getAgencyRouteTagTargetUUID(provider.agencyTag, getRouteTag(provider)) to uuid + +fun VehicleLocationProviderContract.Filter.getTargetUUIDs( + provider: GTFSRealTimeProvider, + includeAgencyTag: Boolean = false, + includeRouteType: Boolean = false, + includeStopTags: Boolean = false, +) = + (poi as? RouteDirectionStop)?.getTargetUUIDs(provider, includeAgencyTag, includeRouteType, includeStopTags) + ?: routeDirection?.getTargetUUIDs(provider, includeAgencyTag, includeRouteType) + ?: route?.getTargetUUIDs(provider, includeAgencyTag, includeRouteType) + fun RouteDirectionStop.getTargetUUIDs( provider: GTFSRealTimeProvider, includeAgencyTag: Boolean = false, @@ -81,14 +130,22 @@ fun RouteDirectionStop.getTargetUUIDs( } } -fun RouteDirection.getTargetUUIDs(provider: GTFSRealTimeProvider, includeAgencyTag: Boolean = false, includeRouteType: Boolean = false) = buildMap { +fun RouteDirection.getTargetUUIDs( + provider: GTFSRealTimeProvider, + includeAgencyTag: Boolean = false, + includeRouteType: Boolean = false, +) = buildMap { if (includeAgencyTag) put(getAgencyTagTargetUUID(provider.agencyTag), authority) if (includeRouteType) getAgencyRouteTypeTagTargetUUID(provider.agencyTag, getRouteTypeTag(provider))?.let { put(it, authority) } put(getAgencyRouteTagTargetUUID(provider.agencyTag, getRouteTag(provider)), route.uuid) getAgencyRouteDirectionTagTargetUUID(provider.agencyTag, getRouteTag(provider), getDirectionTag(provider))?.let { put(it, uuid) } } -fun Route.getTargetUUIDs(provider: GTFSRealTimeProvider, includeAgencyTag: Boolean = false, includeRouteType: Boolean = false) = buildMap { +fun Route.getTargetUUIDs( + provider: GTFSRealTimeProvider, + includeAgencyTag: Boolean = false, + includeRouteType: Boolean = false, +) = buildMap { if (includeAgencyTag) put(getAgencyTagTargetUUID(provider.agencyTag), authority) if (includeRouteType) getAgencyRouteTypeTagTargetUUID(provider.agencyTag, getRouteTypeTag(provider))?.let { put(it, authority) } put(getAgencyRouteTagTargetUUID(provider.agencyTag, getRouteTag(provider)), uuid) diff --git a/src/main/java/org/mtransit/android/commons/provider/status/GTFSRealTimeTripUpdatesProvider.kt b/src/main/java/org/mtransit/android/commons/provider/status/GTFSRealTimeTripUpdatesProvider.kt index ac6ced62..fd4a4d05 100644 --- a/src/main/java/org/mtransit/android/commons/provider/status/GTFSRealTimeTripUpdatesProvider.kt +++ b/src/main/java/org/mtransit/android/commons/provider/status/GTFSRealTimeTripUpdatesProvider.kt @@ -13,7 +13,6 @@ import org.mtransit.android.commons.data.arrival import org.mtransit.android.commons.data.departure import org.mtransit.android.commons.data.toNoData import org.mtransit.android.commons.provider.GTFSRealTimeProvider -import org.mtransit.android.commons.provider.GTFSRealTimeProvider.isIGNORE_DIRECTION import org.mtransit.android.commons.provider.gtfs.GtfsRealTimeStorage import org.mtransit.android.commons.provider.gtfs.GtfsRealtimeExt.optDirectionId import org.mtransit.android.commons.provider.gtfs.GtfsRealtimeExt.optTrip @@ -23,6 +22,7 @@ import org.mtransit.android.commons.provider.gtfs.GtfsRealtimeExt.toTripUpdates import org.mtransit.android.commons.provider.gtfs.getRDS import org.mtransit.android.commons.provider.gtfs.getRDSSchedule import org.mtransit.android.commons.provider.gtfs.getTripIds +import org.mtransit.android.commons.provider.gtfs.ignoreDirection import org.mtransit.android.commons.provider.gtfs.makeRequest import org.mtransit.android.commons.provider.gtfs.parseRouteId import org.mtransit.android.commons.provider.gtfs.parseTripId @@ -106,8 +106,6 @@ object GTFSRealTimeTripUpdatesProvider : MTLog.Loggable { } } - val GTFSRealTimeProvider.ignoreDirection get() = isIGNORE_DIRECTION(this.requireContextCompat()) - private fun GTFSRealTimeProvider.makeCachedStatusFromAgencyData( filter: Schedule.ScheduleStatusFilter, tripIds: List diff --git a/src/main/java/org/mtransit/android/commons/provider/vehiclelocations/GTFSRealTimeVehiclePositionsProvider.kt b/src/main/java/org/mtransit/android/commons/provider/vehiclelocations/GTFSRealTimeVehiclePositionsProvider.kt index 0459555c..86825ef1 100644 --- a/src/main/java/org/mtransit/android/commons/provider/vehiclelocations/GTFSRealTimeVehiclePositionsProvider.kt +++ b/src/main/java/org/mtransit/android/commons/provider/vehiclelocations/GTFSRealTimeVehiclePositionsProvider.kt @@ -5,7 +5,6 @@ import org.mtransit.android.commons.Constants import org.mtransit.android.commons.MTLog import org.mtransit.android.commons.SecurityUtils import org.mtransit.android.commons.TimeUtils -import org.mtransit.android.commons.data.RouteDirectionStop import org.mtransit.android.commons.provider.GTFSRealTimeProvider import org.mtransit.android.commons.provider.GTFSRealTimeProvider.getAgencyRouteDirectionTagTargetUUID import org.mtransit.android.commons.provider.GTFSRealTimeProvider.getAgencyRouteTagTargetUUID @@ -26,8 +25,10 @@ import org.mtransit.android.commons.provider.gtfs.GtfsRealtimeExt.sortVehicles import org.mtransit.android.commons.provider.gtfs.GtfsRealtimeExt.toStringExt import org.mtransit.android.commons.provider.gtfs.GtfsRealtimeExt.toVehicles import org.mtransit.android.commons.provider.gtfs.agencyTag +import org.mtransit.android.commons.provider.gtfs.getPrimaryTargetUUIDs import org.mtransit.android.commons.provider.gtfs.getTargetUUIDs import org.mtransit.android.commons.provider.gtfs.getTripIds +import org.mtransit.android.commons.provider.gtfs.ignoreDirection import org.mtransit.android.commons.provider.gtfs.makeRequest import org.mtransit.android.commons.provider.gtfs.parseRouteId import org.mtransit.android.commons.provider.gtfs.parseTripId @@ -82,9 +83,7 @@ object GTFSRealTimeVehiclePositionsProvider { @JvmStatic fun GTFSRealTimeProvider.getCached(filter: VehicleLocationProviderContract.Filter) = - ((filter.poi as? RouteDirectionStop)?.getTargetUUIDs(this, includeAgencyTag = INCLUDE_AGENCY_TAG) - ?: filter.routeDirection?.getTargetUUIDs(this, includeAgencyTag = INCLUDE_AGENCY_TAG) - ?: filter.route?.getTargetUUIDs(this, includeAgencyTag = INCLUDE_AGENCY_TAG)) + filter.getTargetUUIDs(this, includeAgencyTag = INCLUDE_AGENCY_TAG) ?.let { targetUUIDs -> val tripIds = filter.targetAuthority?.let { targetAuthority -> filter.routeId?.let { routeId -> @@ -95,13 +94,17 @@ object GTFSRealTimeVehiclePositionsProvider { ?.takeIf { tripIds -> tripIds.isNotEmpty() } // trip IDs REQUIRED for GTFS Vehicle locations ?.let { tripIds -> targetUUIDs to tripIds } }?.let { (targetUUIDs, tripIds) -> - getCached(targetUUIDs, tripIds) + getCached(filter, targetUUIDs, tripIds) } - fun GTFSRealTimeProvider.getCached(targetUUIDs: Map, tripIds: List) = buildList { - getCachedVehicleLocationsS(targetUUIDs.keys, tripIds)?.let { - addAll(it) - } + fun GTFSRealTimeProvider.getCached(filter: VehicleLocationProviderContract.Filter, targetUUIDs: Map, tripIds: List) = buildList { + getCachedVehicleLocationsS(targetUUIDs.keys, tripIds)?.takeIf { it.isNotEmpty() } + ?: filter.getPrimaryTargetUUIDs(this@getCached, ignoreDirection = ignoreDirection)?.let { (providerTargetUUID, _) -> + getCachedVehicleLocationsS(providerTargetUUID) // ignore TRIP IDS (outdated?) and try using primary target UUID only (if Route-Direction info available) + } + ?.let { + addAll(it) + } }.map { it.copy(targetUUID = targetUUIDs[it.targetUUID] ?: it.targetUUID) } @JvmStatic @@ -172,7 +175,6 @@ object GTFSRealTimeVehiclePositionsProvider { HttpURLConnection.HTTP_OK -> { val newLastUpdateInMs = TimeUtils.currentTimeMillis() val vehicleLocations = mutableListOf() - val ignoreDirection = GTFSRealTimeProvider.isIGNORE_DIRECTION(context) try { val gFeedMessage = GFeedMessage.parseFrom(response.body.bytes()) val gVehiclePositions = gFeedMessage.entityList.toVehicles() diff --git a/src/main/java/org/mtransit/android/commons/provider/vehiclelocations/VehicleLocationProvider.kt b/src/main/java/org/mtransit/android/commons/provider/vehiclelocations/VehicleLocationProvider.kt index 55530b51..c522a998 100644 --- a/src/main/java/org/mtransit/android/commons/provider/vehiclelocations/VehicleLocationProvider.kt +++ b/src/main/java/org/mtransit/android/commons/provider/vehiclelocations/VehicleLocationProvider.kt @@ -124,6 +124,10 @@ abstract class VehicleLocationProvider : MTContentProvider(), } } + fun

P.getCachedVehicleLocationsS( + targetUUID: String, + ) = getCachedVehicleLocationsS(setOf(targetUUID)) + fun

P.getCachedVehicleLocationsS( targetUUIDs: Collection, tripIds: List? = null, From fc5db3da1e4f8e98663dc83db722ea687b613392 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mathieu=20M=C3=A9a?= Date: Tue, 31 Mar 2026 15:01:39 -0400 Subject: [PATCH 02/17] wip --- .../provider/gtfs/GTFSRealTimeProviderExt.kt | 9 +++---- .../gtfs/GTFSRealTimeProviderFilter.kt | 11 ++++++++ .../GTFSRealTimeServiceAlertsProvider.kt | 15 ++++++----- .../ServiceUpdateProviderContract.java | 6 ++++- .../GTFSRealTimeVehiclePositionsProvider.kt | 27 ++++++++++--------- .../VehicleLocationProviderContract.kt | 10 +++---- 6 files changed, 49 insertions(+), 29 deletions(-) create mode 100644 src/main/java/org/mtransit/android/commons/provider/gtfs/GTFSRealTimeProviderFilter.kt diff --git a/src/main/java/org/mtransit/android/commons/provider/gtfs/GTFSRealTimeProviderExt.kt b/src/main/java/org/mtransit/android/commons/provider/gtfs/GTFSRealTimeProviderExt.kt index 93abebdf..7cc07505 100644 --- a/src/main/java/org/mtransit/android/commons/provider/gtfs/GTFSRealTimeProviderExt.kt +++ b/src/main/java/org/mtransit/android/commons/provider/gtfs/GTFSRealTimeProviderExt.kt @@ -27,7 +27,6 @@ import org.mtransit.android.commons.provider.gtfs.GtfsRealtimeExt.optStopId import org.mtransit.android.commons.provider.gtfs.GtfsRealtimeExt.optTripId import org.mtransit.android.commons.provider.gtfs.GtfsRealtimeExt.originalIdToHash import org.mtransit.android.commons.provider.gtfs.GtfsRealtimeExt.originalIdToId -import org.mtransit.android.commons.provider.vehiclelocations.VehicleLocationProviderContract import java.net.URL import com.google.transit.realtime.GtfsRealtime.EntitySelector as GEntitySelector import com.google.transit.realtime.GtfsRealtime.TripDescriptor as GTripDescriptor @@ -44,11 +43,11 @@ private val GTFSRealTimeProvider.tripIdCleanupPattern get() = getTripIdCleanupPa fun GTFSRealTimeProvider.parseTripId(td: GTripDescriptor) = td.optTripId?.let { parseTripId(it) } fun GTFSRealTimeProvider.parseTripId(gTripId: String) = gTripId.originalIdToId(tripIdCleanupPattern) -private val GTFSRealTimeProvider.stopIdCleanupPattern get() = getStopIdCleanupPattern(requireContextCompat()) -fun GTFSRealTimeProvider.parseStopId(es: GEntitySelector) = es.optStopId?.let { parseStopId(it) } @Suppress("unused") fun GTFSRealTimeProvider.parseStopId(stu: GTUStopTimeUpdate) = stu.optStopId?.let { parseStopId(it) } +fun GTFSRealTimeProvider.parseStopId(es: GEntitySelector) = es.optStopId?.let { parseStopId(it) } fun GTFSRealTimeProvider.parseStopId(gStopId: String) = gStopId.originalIdToHash(stopIdCleanupPattern) +private val GTFSRealTimeProvider.stopIdCleanupPattern get() = getStopIdCleanupPattern(requireContextCompat()) val GTFSRealTimeProvider.agencyTag get() = getAgencyTag(requireContextCompat()) @@ -67,7 +66,7 @@ fun RouteDirectionStop.getRouteTag(provider: GTFSRealTimeProvider) = this.route. fun RouteDirectionStop.getDirectionTag(provider: GTFSRealTimeProvider) = this.direction.getDirectionTag(provider) fun RouteDirectionStop.getStopTag(provider: GTFSRealTimeProvider) = this.stop.getStopTag(provider) -fun VehicleLocationProviderContract.Filter.getPrimaryTargetUUIDs( +fun GTFSRealTimeProviderFilter.getPrimaryTargetUUIDs( provider: GTFSRealTimeProvider, ignoreDirection: Boolean = false, includeStopTags: Boolean = false @@ -101,7 +100,7 @@ fun Route.getPrimaryTargetUUIDs( ) = getAgencyRouteTagTargetUUID(provider.agencyTag, getRouteTag(provider)) to uuid -fun VehicleLocationProviderContract.Filter.getTargetUUIDs( +fun GTFSRealTimeProviderFilter.getTargetUUIDs( provider: GTFSRealTimeProvider, includeAgencyTag: Boolean = false, includeRouteType: Boolean = false, diff --git a/src/main/java/org/mtransit/android/commons/provider/gtfs/GTFSRealTimeProviderFilter.kt b/src/main/java/org/mtransit/android/commons/provider/gtfs/GTFSRealTimeProviderFilter.kt new file mode 100644 index 00000000..0e9fbf78 --- /dev/null +++ b/src/main/java/org/mtransit/android/commons/provider/gtfs/GTFSRealTimeProviderFilter.kt @@ -0,0 +1,11 @@ +package org.mtransit.android.commons.provider.gtfs + +import org.mtransit.android.commons.data.POI +import org.mtransit.android.commons.data.Route +import org.mtransit.android.commons.data.RouteDirection + +interface GTFSRealTimeProviderFilter { + val poi: POI? + val routeDirection: RouteDirection? + val route: Route? +} diff --git a/src/main/java/org/mtransit/android/commons/provider/serviceupdate/GTFSRealTimeServiceAlertsProvider.kt b/src/main/java/org/mtransit/android/commons/provider/serviceupdate/GTFSRealTimeServiceAlertsProvider.kt index 149321e1..fc780383 100644 --- a/src/main/java/org/mtransit/android/commons/provider/serviceupdate/GTFSRealTimeServiceAlertsProvider.kt +++ b/src/main/java/org/mtransit/android/commons/provider/serviceupdate/GTFSRealTimeServiceAlertsProvider.kt @@ -1,7 +1,6 @@ package org.mtransit.android.commons.provider.serviceupdate import org.mtransit.android.commons.MTLog -import org.mtransit.android.commons.data.RouteDirectionStop import org.mtransit.android.commons.data.ServiceUpdate import org.mtransit.android.commons.data.makeServiceUpdateNoneList import org.mtransit.android.commons.provider.GTFSRealTimeProvider @@ -30,22 +29,26 @@ object GTFSRealTimeServiceAlertsProvider { @JvmStatic fun GTFSRealTimeProvider.getCached(filter: ServiceUpdateProviderContract.Filter) = - ((filter.poi as? RouteDirectionStop)?.getTargetUUIDs(this, includeAgencyTag = true, includeRouteType = true, includeStopTags = true) - ?: filter.routeDirection?.getTargetUUIDs(this, includeAgencyTag = true, includeRouteType = true) - ?: filter.route?.getTargetUUIDs(this, includeAgencyTag = true, includeRouteType = true)) + filter.getTargetUUIDs(this, includeAgencyTag = true, includeRouteType = true, includeStopTags = true) ?.let { targetUUIDs -> val tripIds = filter.targetAuthority?.let { targetAuthority -> filter.routeId?.let { routeId -> context?.getTripIds(targetAuthority, routeId, filter.directionId) } } - targetUUIDs to tripIds // trip IDs not required for GTFS Alerts + targetUUIDs to tripIds?.takeIf { it.isNotEmpty() } // trip IDs not required for GTFS Alerts }?.let { (targetUUIDs, tripIds) -> getCached(targetUUIDs, tripIds) } fun GTFSRealTimeProvider.getCached(targetUUIDs: Map, tripIds: List?) = buildList { - getCachedServiceUpdatesS(targetUUIDs.keys, tripIds)?.let { + tripIds?.let { + // trip IDs preferred for all result filtered correctly + getCachedServiceUpdatesS(targetUUIDs.keys, tripIds)?.takeIf { it.isNotEmpty() } + } ?: run { + // fall back to showing all w/o filtering trip IDs + getCachedServiceUpdatesS(targetUUIDs.keys) + }?.let { addAll(it) } }.map { it.apply { targetUUID = targetUUIDs[it.targetUUID] ?: it.targetUUID } } diff --git a/src/main/java/org/mtransit/android/commons/provider/serviceupdate/ServiceUpdateProviderContract.java b/src/main/java/org/mtransit/android/commons/provider/serviceupdate/ServiceUpdateProviderContract.java index 294cde5c..b08e07ef 100644 --- a/src/main/java/org/mtransit/android/commons/provider/serviceupdate/ServiceUpdateProviderContract.java +++ b/src/main/java/org/mtransit/android/commons/provider/serviceupdate/ServiceUpdateProviderContract.java @@ -19,6 +19,7 @@ import org.mtransit.android.commons.data.ServiceUpdate; import org.mtransit.android.commons.data.Targetable; import org.mtransit.android.commons.provider.common.ProviderContract; +import org.mtransit.android.commons.provider.gtfs.GTFSRealTimeProviderFilter; import java.util.HashMap; import java.util.List; @@ -96,7 +97,7 @@ class Columns { } @SuppressWarnings("WeakerAccess") - class Filter implements MTLog.Loggable { + class Filter implements GTFSRealTimeProviderFilter, MTLog.Loggable { private static final String LOG_TAG = ServiceUpdateProviderContract.class.getSimpleName() + ">" + Filter.class.getSimpleName(); @@ -233,16 +234,19 @@ public Long getDirectionId() { } @Nullable + @Override public POI getPoi() { return poi; } @Nullable + @Override public Route getRoute() { return route; } @Nullable + @Override public RouteDirection getRouteDirection() { return routeDirection; } diff --git a/src/main/java/org/mtransit/android/commons/provider/vehiclelocations/GTFSRealTimeVehiclePositionsProvider.kt b/src/main/java/org/mtransit/android/commons/provider/vehiclelocations/GTFSRealTimeVehiclePositionsProvider.kt index 86825ef1..de3c0983 100644 --- a/src/main/java/org/mtransit/android/commons/provider/vehiclelocations/GTFSRealTimeVehiclePositionsProvider.kt +++ b/src/main/java/org/mtransit/android/commons/provider/vehiclelocations/GTFSRealTimeVehiclePositionsProvider.kt @@ -90,21 +90,24 @@ object GTFSRealTimeVehiclePositionsProvider { context?.getTripIds(targetAuthority, routeId, filter.directionId) } } - tripIds - ?.takeIf { tripIds -> tripIds.isNotEmpty() } // trip IDs REQUIRED for GTFS Vehicle locations - ?.let { tripIds -> targetUUIDs to tripIds } + targetUUIDs to tripIds?.takeIf { it.isNotEmpty() } // no trip IDS == fallback to primary target UUID only }?.let { (targetUUIDs, tripIds) -> - getCached(filter, targetUUIDs, tripIds) + getCached( + primaryTargetUUID = filter.getPrimaryTargetUUIDs(this@getCached, ignoreDirection = ignoreDirection), + targetUUIDs = targetUUIDs, + tripIds = tripIds, + ) } - fun GTFSRealTimeProvider.getCached(filter: VehicleLocationProviderContract.Filter, targetUUIDs: Map, tripIds: List) = buildList { - getCachedVehicleLocationsS(targetUUIDs.keys, tripIds)?.takeIf { it.isNotEmpty() } - ?: filter.getPrimaryTargetUUIDs(this@getCached, ignoreDirection = ignoreDirection)?.let { (providerTargetUUID, _) -> - getCachedVehicleLocationsS(providerTargetUUID) // ignore TRIP IDS (outdated?) and try using primary target UUID only (if Route-Direction info available) - } - ?.let { - addAll(it) - } + fun GTFSRealTimeProvider.getCached(primaryTargetUUID: Pair?, targetUUIDs: Map, tripIds: List?) = buildList { + tripIds?.let { // trip IDs preferred for all result filtered correctly + getCachedVehicleLocationsS(targetUUIDs.keys, tripIds)?.takeIf { it.isNotEmpty() } + } ?: primaryTargetUUID?.let { (providerTargetUUID, _) -> + // fall back to: ignore TRIP IDS (outdated?) and try using primary target UUID only (ex: Route-Direction IDs available) + getCachedVehicleLocationsS(providerTargetUUID) + }?.let { + addAll(it) + } }.map { it.copy(targetUUID = targetUUIDs[it.targetUUID] ?: it.targetUUID) } @JvmStatic diff --git a/src/main/java/org/mtransit/android/commons/provider/vehiclelocations/VehicleLocationProviderContract.kt b/src/main/java/org/mtransit/android/commons/provider/vehiclelocations/VehicleLocationProviderContract.kt index 08ce74e9..628676f8 100644 --- a/src/main/java/org/mtransit/android/commons/provider/vehiclelocations/VehicleLocationProviderContract.kt +++ b/src/main/java/org/mtransit/android/commons/provider/vehiclelocations/VehicleLocationProviderContract.kt @@ -8,7 +8,6 @@ import org.json.JSONException import org.json.JSONObject import org.mtransit.android.commons.JSONUtils import org.mtransit.android.commons.MTLog -import org.mtransit.android.commons.MTLog.Loggable import org.mtransit.android.commons.SecureStringUtils import org.mtransit.android.commons.data.DefaultPOI import org.mtransit.android.commons.data.Direction @@ -17,6 +16,7 @@ import org.mtransit.android.commons.data.Route import org.mtransit.android.commons.data.RouteDirection import org.mtransit.android.commons.data.RouteDirectionStop import org.mtransit.android.commons.provider.common.ProviderContract +import org.mtransit.android.commons.provider.gtfs.GTFSRealTimeProviderFilter import org.mtransit.android.commons.provider.vehiclelocations.model.VehicleLocation import org.mtransit.commons.mapNotNullToMap @@ -92,10 +92,10 @@ interface VehicleLocationProviderContract : ProviderContract { data class Filter @Discouraged("use from() instead") constructor( val authority: String, - val poi: POI? = null, // RouteDirectionStop or DefaultPOI - val route: Route? = null, - val routeDirection: RouteDirection? = null, - ) : Loggable { + override val poi: POI? = null, // RouteDirectionStop or DefaultPOI + override val route: Route? = null, + override val routeDirection: RouteDirection? = null, + ) : GTFSRealTimeProviderFilter, MTLog.Loggable { var inFocus: Boolean? = null val inFocusOrDefault get() = inFocus ?: false From 1ccd2cef27ed3ae96cf68acc33c8330c20aece49 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mathieu=20M=C3=A9a?= Date: Wed, 1 Apr 2026 15:35:53 -0400 Subject: [PATCH 03/17] wip --- .../java/org/mtransit/android/MtLogExt.kt | 1 + .../org/mtransit/android/commons/MTLog.java | 18 ++- .../android/commons/data/ScheduleExt.kt | 35 +++++- .../provider/GTFSRealTimeProvider.java | 3 +- .../provider/gtfs/GTFSRealTimeProviderExt.kt | 4 +- .../provider/gtfs/GTFSStatusProvider.java | 15 ++- .../commons/provider/gtfs/GtfsRealtimeExt.kt | 3 +- .../provider/gtfs/GtfsStatusProviderExt.kt | 24 +++- .../status/GTFSRealTimeTripUpdatesProvider.kt | 118 ++++++++++++++++-- .../GTFSRealTimeTripUpdatesProviderExt.kt | 6 +- 10 files changed, 198 insertions(+), 29 deletions(-) diff --git a/src/main/java/org/mtransit/android/MtLogExt.kt b/src/main/java/org/mtransit/android/MtLogExt.kt index 0cbb033d..67daa359 100644 --- a/src/main/java/org/mtransit/android/MtLogExt.kt +++ b/src/main/java/org/mtransit/android/MtLogExt.kt @@ -8,6 +8,7 @@ fun Long?.toDateTimeLog() = MTLog.formatDateTime(this) fun Date?.toDateTimeLog() = this?.time.toDateTimeLog() +@Suppress("unused") fun Calendar?.toDateTimeLog() = this?.time.toDateTimeLog() @Deprecated("Use toDateTimeLog() instead", ReplaceWith("this.toDateTimeLog()")) diff --git a/src/main/java/org/mtransit/android/commons/MTLog.java b/src/main/java/org/mtransit/android/commons/MTLog.java index e1e01e73..b80d9d5e 100644 --- a/src/main/java/org/mtransit/android/commons/MTLog.java +++ b/src/main/java/org/mtransit/android/commons/MTLog.java @@ -378,11 +378,25 @@ private static String getLogMsg(@NonNull String tag, @NonNull String logMsg) { if (Constants.DEBUG) { logMsg = StringUtils.oneLineOneSpace(logMsg); } - final String time = BuildConfig.DEBUG ? LOG_TIME_FORMAT.formatThreadSafe(TimeUtils.currentTimeMillis()) - : String.valueOf(TimeUtils.currentTimeMillis()); + final String time = makeTime(TimeUtils.currentTimeMillis()); return String.format("%s:%s>%s", time, tag, logMsg); } + @Nullable + public static String makeTime(@Nullable Calendar calendar) { + return calendar == null ? null : makeTime(calendar.getTimeInMillis()); + } + + @Nullable + public static String makeTime(@Nullable Long timeInMs) { + return timeInMs == null ? null : makeTime(timeInMs.longValue()); + } + + @NonNull + public static String makeTime(long timeMs) { + return BuildConfig.DEBUG ? LOG_TIME_FORMAT.formatThreadSafe(timeMs) : String.valueOf(timeMs); + } + private static void logEntireMessage(@NonNull LogMethod logMethod, String logMsg) { if (logMsg.length() < MAX_LOG_CAT_LENGTH) { logMethod.callLog( diff --git a/src/main/java/org/mtransit/android/commons/data/ScheduleExt.kt b/src/main/java/org/mtransit/android/commons/data/ScheduleExt.kt index 7a1c0e27..fc713c9f 100644 --- a/src/main/java/org/mtransit/android/commons/data/ScheduleExt.kt +++ b/src/main/java/org/mtransit/android/commons/data/ScheduleExt.kt @@ -1,16 +1,38 @@ package org.mtransit.android.commons.data import org.mtransit.android.commons.Constants -import org.mtransit.android.commons.MTLog import org.mtransit.android.commons.floorBy import org.mtransit.android.commons.millisToInstant import org.mtransit.android.commons.roundToNearest import org.mtransit.android.commons.toMillis +import org.mtransit.android.toDateTimeLog import kotlin.time.Duration import kotlin.time.Duration.Companion.milliseconds import kotlin.time.Duration.Companion.seconds import kotlin.time.Instant +fun makeSchedule( + id: Int? = null, + targetUUID: String, + lastUpdateInMs: Long, + validityInMs: Long, + readFromSourceAtInMs: Long, + providerPrecisionInMs: Long, + isNoPickup: Boolean = false, + sourceLabel: String? = null, + noData: Boolean = false +) = Schedule( + id, + targetUUID, + lastUpdateInMs, + validityInMs, + readFromSourceAtInMs, + providerPrecisionInMs, + isNoPickup, + sourceLabel, + noData, +) + fun Schedule.toNoData() = Schedule( id, targetUUID, @@ -132,15 +154,18 @@ val Schedule.hasRealTime get() = this.timestamps.any { it.isRealTime } fun Schedule.Timestamp.toStringShort() = buildString { append("T{") arrivalTIfDifferent?.let { - append("a=").append(if (Constants.DEBUG) MTLog.formatDateTime(arrivalT) else arrivalT) + append("a=").append(if (Constants.DEBUG) arrivalT.toDateTimeLog() else arrivalT) if (originalArrivalDelayMs != 0L) { - append("[+/-:").append(if (Constants.DEBUG) MTLog.formatDuration(originalArrivalDelayMs) else originalArrivalDelayMs).append("]") + append("[+/-:").append(if (Constants.DEBUG) originalArrivalDelayMs.toDateTimeLog() else originalArrivalDelayMs).append("]") } append(",") } - append("d=").append(if (Constants.DEBUG) MTLog.formatDateTime(departureT) else departureT) + append("d=").append(if (Constants.DEBUG) departureT.toDateTimeLog() else departureT) if (originalDepartureDelayMs != 0L) { - append("[+/-:").append(if (Constants.DEBUG) MTLog.formatDuration(originalDepartureDelayMs) else originalDepartureDelayMs).append("]") + append("[+/-:").append(if (Constants.DEBUG) originalDepartureDelayMs.toDateTimeLog() else originalDepartureDelayMs).append("]") + } + if (tripId != null) { + append("[tId:").append(tripId).append("]") } if (isRealTime) { append("[RT]") diff --git a/src/main/java/org/mtransit/android/commons/provider/GTFSRealTimeProvider.java b/src/main/java/org/mtransit/android/commons/provider/GTFSRealTimeProvider.java index d30eccae..f581f774 100644 --- a/src/main/java/org/mtransit/android/commons/provider/GTFSRealTimeProvider.java +++ b/src/main/java/org/mtransit/android/commons/provider/GTFSRealTimeProvider.java @@ -545,7 +545,8 @@ private static String getAGENCY_TIME_AM_PM_FORMAT(@NonNull Context context) { /** * Override if multiple {@link GTFSRealTimeProvider} implementations in same app. */ - private static String getAGENCY_TIME_ZONE(@NonNull Context context) { + @NonNull + public static String getAGENCY_TIME_ZONE(@NonNull Context context) { if (agencyTimeZone == null) { agencyTimeZone = context.getResources().getString(R.string.gtfs_real_time_agency_time_zone); } diff --git a/src/main/java/org/mtransit/android/commons/provider/gtfs/GTFSRealTimeProviderExt.kt b/src/main/java/org/mtransit/android/commons/provider/gtfs/GTFSRealTimeProviderExt.kt index 7cc07505..5dbacb76 100644 --- a/src/main/java/org/mtransit/android/commons/provider/gtfs/GTFSRealTimeProviderExt.kt +++ b/src/main/java/org/mtransit/android/commons/provider/gtfs/GTFSRealTimeProviderExt.kt @@ -10,6 +10,7 @@ import org.mtransit.android.commons.data.RouteDirectionStop import org.mtransit.android.commons.data.Stop import org.mtransit.android.commons.provider.GTFSRealTimeProvider import org.mtransit.android.commons.provider.GTFSRealTimeProvider.MT_HASH_SECRET_AND_DATE +import org.mtransit.android.commons.provider.GTFSRealTimeProvider.getAGENCY_TIME_ZONE import org.mtransit.android.commons.provider.GTFSRealTimeProvider.getAGENCY_URL_HEADER_NAMES import org.mtransit.android.commons.provider.GTFSRealTimeProvider.getAGENCY_URL_HEADER_VALUES import org.mtransit.android.commons.provider.GTFSRealTimeProvider.getAGENCY_URL_TOKEN @@ -32,7 +33,8 @@ import com.google.transit.realtime.GtfsRealtime.EntitySelector as GEntitySelecto import com.google.transit.realtime.GtfsRealtime.TripDescriptor as GTripDescriptor import com.google.transit.realtime.GtfsRealtime.TripUpdate.StopTimeUpdate as GTUStopTimeUpdate -val GTFSRealTimeProvider.ignoreDirection get() = isIGNORE_DIRECTION(this.requireContextCompat()) +val GTFSRealTimeProvider.ignoreDirection get() = isIGNORE_DIRECTION(requireContextCompat()) +val GTFSRealTimeProvider.timeZone get() = getAGENCY_TIME_ZONE(requireContextCompat()) private val GTFSRealTimeProvider.routeIdCleanupPattern get() = getRouteIdCleanupPattern(requireContextCompat()) fun GTFSRealTimeProvider.parseRouteId(es: GEntitySelector) = es.optRouteId?.let { parseRouteId(it) } diff --git a/src/main/java/org/mtransit/android/commons/provider/gtfs/GTFSStatusProvider.java b/src/main/java/org/mtransit/android/commons/provider/gtfs/GTFSStatusProvider.java index 4d4cf024..cad57193 100644 --- a/src/main/java/org/mtransit/android/commons/provider/gtfs/GTFSStatusProvider.java +++ b/src/main/java/org/mtransit/android/commons/provider/gtfs/GTFSStatusProvider.java @@ -151,7 +151,7 @@ public static POIStatus getNewStatus(@NonNull GTFSProvider provider, @NonNull St final Schedule schedule = new Schedule( null, scheduleStatusFilter.getTargetUUID(), - scheduleStatusFilter.getTimestampOrDefault(), + scheduleStatusFilter.getTimestampOrDefault(), // NOW getStatusMaxValidityInMs(), PROVIDER_READ_FROM_SOURCE_AT_IN_MS, PROVIDER_PRECISION_IN_MS, @@ -233,14 +233,14 @@ private static String getROUTE_FREQUENCY_RAW_FILE_FORMAT(@NonNull Context contex private static final int GTFS_ROUTE_FREQUENCY_FILE_COL_COUNT = 5; @NonNull - private static ArrayList findTimestamps(@NonNull GTFSProvider provider, Schedule.ScheduleStatusFilter filter) { + private static ArrayList findTimestamps(@NonNull GTFSProvider provider, @NonNull Schedule.ScheduleStatusFilter filter) { ArrayList allTimestamps = new ArrayList<>(); final RouteDirectionStop rds = filter.getRouteDirectionStop(); final int maxDataRequests = filter.getMaxDataRequestsOrDefault(); final int minUsefulResults = filter.getMinUsefulResultsOrDefault(); final long minDurationCoveredInMs = filter.getMinUsefulDurationCoveredInMsOrDefault(); final long lookBehindInMs = filter.getLookBehindInMsOrDefault(); - final long timestamp = filter.getTimestampOrDefault(); + final long timestamp = filter.getTimestampOrDefault(); // NOW final long minTimestampCoveredIntMs = timestamp + minDurationCoveredInMs; final Context context = provider.requireContextCompat(); final ThreadSafeDateFormatter dateFormat = getDateFormat(context); @@ -324,7 +324,12 @@ private static ArrayList findTimestamps(@NonNull GTFSProvide return allTimestamps; } - protected static void alignLookupStartTime(Integer lastServiceDate, ThreadSafeDateFormatter dateFormat, Calendar lookupStartAt, long lastDepartureInMs) { + static void alignLookupStartTime( + @Nullable Integer lastServiceDate, + @NonNull ThreadSafeDateFormatter dateFormat, + @NonNull Calendar lookupStartAt, + long lastDepartureInMs + ) { if (lastServiceDate != null) { try { while (Integer.parseInt(dateFormat.formatThreadSafe(lookupStartAt)) > lastServiceDate) { @@ -593,7 +598,7 @@ private static ArrayList findFrequencies(@NonNull GTFSProvid final RouteDirectionStop rds = filter.getRouteDirectionStop(); final int maxDataRequests = filter.getMaxDataRequestsOrDefault(); final long minDurationCoveredInMs = filter.getMinUsefulDurationCoveredInMsOrDefault(); - final long timestamp = filter.getTimestampOrDefault(); + final long timestamp = filter.getTimestampOrDefault(); // NOW final long minTimestampCovered = timestamp + minDurationCoveredInMs; final Context context = provider.requireContextCompat(); final ThreadSafeDateFormatter dateFormat = getDateFormat(context); diff --git a/src/main/java/org/mtransit/android/commons/provider/gtfs/GtfsRealtimeExt.kt b/src/main/java/org/mtransit/android/commons/provider/gtfs/GtfsRealtimeExt.kt index ffd2c64d..6dc8e646 100644 --- a/src/main/java/org/mtransit/android/commons/provider/gtfs/GtfsRealtimeExt.kt +++ b/src/main/java/org/mtransit/android/commons/provider/gtfs/GtfsRealtimeExt.kt @@ -276,7 +276,7 @@ object GtfsRealtimeExt { append( buildList { optDelay?.let { add(if (short) "d=$it" else "delay=$it") } - optTime?.let { add(if (short) "t=$it" else "time=$it") } + optTimeMs?.let { add(if (short) "t=${it.toDateTimeLog()}" else "time=${it.toDateTimeLog()}") } optUncertainty?.let { add(if (short) "u=$it" else "uncertainty=$it") } optScheduledTime?.let { add(if (short) "sT=$it" else "schedTime=$it") } }.joinToStringList() @@ -286,6 +286,7 @@ object GtfsRealtimeExt { val GTUStopTimeEvent.optDelay get() = if (hasDelay()) delay else null val GTUStopTimeEvent.optDelayDuration: Duration? get() = this.optDelay?.seconds val GTUStopTimeEvent.optTime get() = if (hasTime()) time else null + val GTUStopTimeEvent.optTimeMs get() = optTime?.secToMs() val GTUStopTimeEvent.optTimeInstant get() = if (hasTime()) time.secsToInstant() else null val GTUStopTimeEvent.optUncertainty get() = if (hasUncertainty()) uncertainty else null val GTUStopTimeEvent.optScheduledTime get() = if (hasScheduledTime()) scheduledTime else null diff --git a/src/main/java/org/mtransit/android/commons/provider/gtfs/GtfsStatusProviderExt.kt b/src/main/java/org/mtransit/android/commons/provider/gtfs/GtfsStatusProviderExt.kt index 4e92f2de..cf7a9968 100644 --- a/src/main/java/org/mtransit/android/commons/provider/gtfs/GtfsStatusProviderExt.kt +++ b/src/main/java/org/mtransit/android/commons/provider/gtfs/GtfsStatusProviderExt.kt @@ -7,20 +7,37 @@ import org.mtransit.android.commons.UriUtils import org.mtransit.android.commons.data.RouteDirectionStop import org.mtransit.android.commons.data.Schedule import org.mtransit.android.commons.provider.status.StatusProviderContract +import kotlin.time.Duration import kotlin.time.Duration.Companion.hours +private const val DEFAULT_MAX_DATA_REQUEST = 3 // yesterday service ending + today + tomorrow? +private val DEFAULT_LOOK_BEHIND = 1.hours + fun Context.getRDSSchedule( authority: String, rdsList: Iterable, includeCancelledTimestamps: Boolean = false, + lookBehind: Duration = DEFAULT_LOOK_BEHIND, + minCovered: Duration? = null, + maxDataRequest: Int = DEFAULT_MAX_DATA_REQUEST, ) = rdsList.mapNotNull { - getRDSSchedule(authority, it, includeCancelledTimestamps) + getRDSSchedule( + authority = authority, + rds = it, + includeCancelledTimestamps = includeCancelledTimestamps, + lookBehind = lookBehind, + minCovered = minCovered, + maxDataRequest = maxDataRequest, + ) } fun Context.getRDSSchedule( authority: String, rds: RouteDirectionStop, includeCancelledTimestamps: Boolean = false, + lookBehind: Duration = DEFAULT_LOOK_BEHIND, + minCovered: Duration? = null, + maxDataRequest: Int = DEFAULT_MAX_DATA_REQUEST, ): Schedule? = try { contentResolver.query( Uri.withAppendedPath( @@ -29,8 +46,9 @@ fun Context.getRDSSchedule( ), StatusProviderContract.PROJECTION_STATUS, Schedule.ScheduleStatusFilter(rds).apply { - setLookBehindInMs(1.hours.inWholeMilliseconds) - setMaxDataRequests(3) // yesterday service ending + today + tomorrow? + setLookBehindInMs(lookBehind.inWholeMilliseconds) + setMinUsefulDurationCoveredInMs(minCovered?.inWholeMilliseconds) + setMaxDataRequests(maxDataRequest) setIncludeCancelledTimestamps(includeCancelledTimestamps) }.let { it.toJSONStringStatic(it) }, null, diff --git a/src/main/java/org/mtransit/android/commons/provider/status/GTFSRealTimeTripUpdatesProvider.kt b/src/main/java/org/mtransit/android/commons/provider/status/GTFSRealTimeTripUpdatesProvider.kt index fd4a4d05..a2ed0669 100644 --- a/src/main/java/org/mtransit/android/commons/provider/status/GTFSRealTimeTripUpdatesProvider.kt +++ b/src/main/java/org/mtransit/android/commons/provider/status/GTFSRealTimeTripUpdatesProvider.kt @@ -7,14 +7,24 @@ import org.mtransit.android.commons.MTLog import org.mtransit.android.commons.SecurityUtils import org.mtransit.android.commons.TimeUtils import org.mtransit.android.commons.TimeUtilsK +import org.mtransit.android.commons.data.Direction import org.mtransit.android.commons.data.POIStatus +import org.mtransit.android.commons.data.RouteDirectionStop import org.mtransit.android.commons.data.Schedule import org.mtransit.android.commons.data.arrival import org.mtransit.android.commons.data.departure +import org.mtransit.android.commons.data.makeSchedule import org.mtransit.android.commons.data.toNoData import org.mtransit.android.commons.provider.GTFSRealTimeProvider import org.mtransit.android.commons.provider.gtfs.GtfsRealTimeStorage +import org.mtransit.android.commons.provider.gtfs.GtfsRealtimeExt.optArrival +import org.mtransit.android.commons.provider.gtfs.GtfsRealtimeExt.optDeparture import org.mtransit.android.commons.provider.gtfs.GtfsRealtimeExt.optDirectionId +import org.mtransit.android.commons.provider.gtfs.GtfsRealtimeExt.optRouteId +import org.mtransit.android.commons.provider.gtfs.GtfsRealtimeExt.optStopId +import org.mtransit.android.commons.provider.gtfs.GtfsRealtimeExt.optStopTimeUpdateList +import org.mtransit.android.commons.provider.gtfs.GtfsRealtimeExt.optTime +import org.mtransit.android.commons.provider.gtfs.GtfsRealtimeExt.optTimeMs import org.mtransit.android.commons.provider.gtfs.GtfsRealtimeExt.optTrip import org.mtransit.android.commons.provider.gtfs.GtfsRealtimeExt.sortTripUpdates import org.mtransit.android.commons.provider.gtfs.GtfsRealtimeExt.toStringExt @@ -25,7 +35,9 @@ import org.mtransit.android.commons.provider.gtfs.getTripIds import org.mtransit.android.commons.provider.gtfs.ignoreDirection import org.mtransit.android.commons.provider.gtfs.makeRequest import org.mtransit.android.commons.provider.gtfs.parseRouteId +import org.mtransit.android.commons.provider.gtfs.parseStopId import org.mtransit.android.commons.provider.gtfs.parseTripId +import org.mtransit.android.commons.provider.gtfs.timeZone import org.mtransit.commons.SourceUtils import java.io.File import java.io.IOException @@ -38,6 +50,7 @@ import kotlin.time.Duration.Companion.hours import kotlin.time.Duration.Companion.minutes import kotlin.time.Duration.Companion.seconds import com.google.transit.realtime.GtfsRealtime.FeedMessage as GFeedMessage +import com.google.transit.realtime.GtfsRealtime.TripUpdate as GTripUpdate object GTFSRealTimeTripUpdatesProvider : MTLog.Loggable { @@ -78,7 +91,7 @@ object GTFSRealTimeTripUpdatesProvider : MTLog.Loggable { @JvmStatic fun GTFSRealTimeProvider.getCached(statusFilter: StatusProviderContract.Filter): POIStatus? { val filter = statusFilter as? Schedule.ScheduleStatusFilter ?: run { - MTLog.w(this@getCached, "getCached() > Can't find new schedule without schedule filter!") + MTLog.w(this@GTFSRealTimeTripUpdatesProvider, "getCached() > Can't find new schedule without schedule filter!") return null } val tripIds = filter.targetAuthority.let { targetAuthority -> @@ -102,15 +115,15 @@ object GTFSRealTimeTripUpdatesProvider : MTLog.Loggable { if (GtfsRealTimeStorage.getTripUpdateLastUpdateMs(context, 0L) <= 0L) return null // never loaded synchronized(tripUpdateLock.getOrPut(filter.routeDirectionStop.routeDirectionUUID) { Any() }) { return getCachedStatusS(filter.targetUUID, tripIds) // try another time - ?: makeCachedStatusFromAgencyData(filter, tripIds) + ?: makeCachedStatusFromAgencyData(context, filter, tripIds) } } private fun GTFSRealTimeProvider.makeCachedStatusFromAgencyData( + context: Context, filter: Schedule.ScheduleStatusFilter, tripIds: List ): POIStatus? { - val context = context ?: return null val gtfsRealTimeTripUpdateFile = File(context.cacheDir, GTFS_RT_TRIP_UPDATE_PB_FILE_NAME) if (!gtfsRealTimeTripUpdateFile.exists()) return null val readFromSourceMs = GtfsRealTimeStorage.getTripUpdateLastUpdateMs(context, 0L) @@ -146,13 +159,22 @@ object GTFSRealTimeTripUpdatesProvider : MTLog.Loggable { } return@filter true }.takeIf { it.isNotEmpty() } - rdTripUpdates ?: return null + rdTripUpdates ?: return makeCachedStatusFromAgencyDataFallback( + rds = rds, + filter = filter, + tripIds = tripIds, + readFromSourceMs = readFromSourceMs, + sourceLabel = sourceLabel, + gTripUpdates = gTripUpdates, + getRDS = { context.getRDS(rds.authority, routeId, directionId) } + ) if (Constants.DEBUG) { + MTLog.d( + this@GTFSRealTimeTripUpdatesProvider, + "makeCachedStatusFromAgencyData() > GTFS {R:'${rds.route.shortestName}'|D:${rds.direction.headsignValue}} [${gTripUpdates.size}]: " + ) rdTripUpdates.forEach { (_, gTripUpdate) -> - MTLog.d( - this@GTFSRealTimeTripUpdatesProvider, - "makeCachedStatusFromAgencyData() > GTFS [R:'${rds.route.shortestName}'|D:${rds.direction.headsignValue}] trip update: ${gTripUpdate.toStringExt()}." - ) + MTLog.d(this@GTFSRealTimeTripUpdatesProvider, "makeCachedStatusFromAgencyData() > - GTFS ${gTripUpdate.toStringExt()}.") } } val sortedRDS = context.getRDS(rds.authority, routeId, directionId) @@ -206,10 +228,88 @@ object GTFSRealTimeTripUpdatesProvider : MTLog.Loggable { } } + private fun GTFSRealTimeProvider.makeCachedStatusFromAgencyDataFallback( + rds: RouteDirectionStop, + filter: Schedule.ScheduleStatusFilter, + tripIds: List, + readFromSourceMs: Long, + sourceLabel: String, + gTripUpdates: List, + getRDS: () -> List?, + ): POIStatus? { + val rdTripUpdates = gTripUpdates + .filter { gTripUpdate -> + val td = gTripUpdate.optTrip ?: return@filter false + parseRouteId(td)?.let { routeIdHash -> + if (routeIdHash != rds.route.originalIdHash.toString()) { + return@filter false + } + } + td.optDirectionId?.takeIf { !ignoreDirection }?.let { directionId -> + if (directionId != rds.direction.originalDirectionIdOrNull) { + return@filter false + } + } + return@filter true + }.takeIf { it.isNotEmpty() } + ?: return null + val notAllWithTime = rdTripUpdates.flatMap { it.optStopTimeUpdateList.orEmpty() }.any { it.departure.optTime == null } + if (notAllWithTime) return null + val uuidSchedule = mutableMapOf() + val sortedRDS = getRDS() + rdTripUpdates.groupBy { it.optTrip }.forEach { (gTripDescriptor, gTripUpdates) -> + gTripDescriptor ?: return@forEach + val tdRouteIdHasString = gTripDescriptor.optRouteId?.let { parseRouteId(it) } ?: return@forEach + val tdDirectionId = gTripDescriptor.optDirectionId + if (!ignoreDirection && tdDirectionId == null) return@forEach + gTripUpdates.forEach { gTripUpdate -> + gTripUpdate.optStopTimeUpdateList?.forEach { gStopTimeUpdate -> + val stuStopIdHash = gStopTimeUpdate.optStopId?.let { parseStopId(it) } ?: return@forEach + val departureMs = gStopTimeUpdate.optDeparture?.optTimeMs ?: return@forEach + val targetUUID = sortedRDS?.singleOrNull { rds -> + if (rds.route.originalIdHash.toString() != tdRouteIdHasString) return@singleOrNull false + if (!rds.stop.isSameOriginalId(stuStopIdHash)) return@singleOrNull false + if (!ignoreDirection && tdDirectionId != rds.direction.originalDirectionIdOrNull) return@singleOrNull false + return@singleOrNull true + }?.uuid ?: return@forEach + val rdsSchedule = uuidSchedule.getOrPut(targetUUID) { + makeSchedule( + targetUUID = targetUUID, + lastUpdateInMs = readFromSourceMs, + validityInMs = TRIP_UPDATE_VALIDITY_IN_MS, + readFromSourceAtInMs = readFromSourceMs, + providerPrecisionInMs = PROVIDER_PRECISION_IN_MS, + sourceLabel = sourceLabel, + noData = false + ) + } + rdsSchedule.addTimestampWithoutSort( + Schedule.Timestamp( + departureMs, + timeZone + ).apply { + realTime = true + gStopTimeUpdate.optArrival?.optTimeMs?.let { this.arrivalT = it } + gStopTimeUpdate?.stopTimeProperties?.let { stp -> + stp.stopHeadsign?.takeIf { it.isNotBlank() }?.let { this.setHeadsign(Direction.HEADSIGN_TYPE_STRING, it) } + } + } + ) + } + } + } + uuidSchedule.takeIf { it.isNotEmpty() } ?: return null + uuidSchedule.values.forEach { schedule -> + schedule.sortTimestamps() + cacheStatus(schedule) + } + return getCachedStatusS(filter.targetUUID, tripIds) + } + @JvmStatic fun GTFSRealTimeProvider.getNew(statusFilter: StatusProviderContract.Filter): POIStatus? { val filter = statusFilter as? Schedule.ScheduleStatusFilter ?: run { - MTLog.w(this, "getNew() > Can't find new schedule without schedule filter!") + MTLog.w(this@GTFSRealTimeTripUpdatesProvider, "getNew() > Can't find new schedule without schedule filter!") return null } updateAgencyDataIfRequired(filter.isInFocusOrDefault) diff --git a/src/main/java/org/mtransit/android/commons/provider/status/GTFSRealTimeTripUpdatesProviderExt.kt b/src/main/java/org/mtransit/android/commons/provider/status/GTFSRealTimeTripUpdatesProviderExt.kt index 6581e0c7..7917ecb1 100644 --- a/src/main/java/org/mtransit/android/commons/provider/status/GTFSRealTimeTripUpdatesProviderExt.kt +++ b/src/main/java/org/mtransit/android/commons/provider/status/GTFSRealTimeTripUpdatesProviderExt.kt @@ -164,10 +164,12 @@ internal fun processRDTripUpdate( } } -fun Iterable.findClosestTripTimestamp(tripId: String, stopSequence: Int) = +fun Iterable.findClosestTripTimestamp(tripId: String, stopSequence: Int? = null) = this.filter { it.tripId == tripId } .filter { timestamp -> - (timestamp.stopSequenceOrNull == null || timestamp.stopSequenceOrNull == stopSequence) + timestamp.stopSequenceOrNull ?: return@filter true // keep + stopSequence ?: return@filter true // keep + return@filter timestamp.stopSequenceOrNull == stopSequence }.let { rdsTripTimestamps -> if (rdsTripTimestamps.size > 1) { val now = TimeUtilsK.currentInstant() From d94f5e33f3f216798c11f0f7be9c8f9355cd88f5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mathieu=20M=C3=A9a?= Date: Wed, 1 Apr 2026 15:44:24 -0400 Subject: [PATCH 04/17] wip --- .../android/commons/provider/gtfs/GtfsStatusProviderExt.kt | 4 ---- .../serviceupdate/GTFSRealTimeServiceAlertsProvider.kt | 2 +- .../provider/status/GTFSRealTimeTripUpdatesProvider.kt | 1 + .../vehiclelocations/GTFSRealTimeVehiclePositionsProvider.kt | 4 +++- 4 files changed, 5 insertions(+), 6 deletions(-) diff --git a/src/main/java/org/mtransit/android/commons/provider/gtfs/GtfsStatusProviderExt.kt b/src/main/java/org/mtransit/android/commons/provider/gtfs/GtfsStatusProviderExt.kt index cf7a9968..e9365f37 100644 --- a/src/main/java/org/mtransit/android/commons/provider/gtfs/GtfsStatusProviderExt.kt +++ b/src/main/java/org/mtransit/android/commons/provider/gtfs/GtfsStatusProviderExt.kt @@ -18,7 +18,6 @@ fun Context.getRDSSchedule( rdsList: Iterable, includeCancelledTimestamps: Boolean = false, lookBehind: Duration = DEFAULT_LOOK_BEHIND, - minCovered: Duration? = null, maxDataRequest: Int = DEFAULT_MAX_DATA_REQUEST, ) = rdsList.mapNotNull { getRDSSchedule( @@ -26,7 +25,6 @@ fun Context.getRDSSchedule( rds = it, includeCancelledTimestamps = includeCancelledTimestamps, lookBehind = lookBehind, - minCovered = minCovered, maxDataRequest = maxDataRequest, ) } @@ -36,7 +34,6 @@ fun Context.getRDSSchedule( rds: RouteDirectionStop, includeCancelledTimestamps: Boolean = false, lookBehind: Duration = DEFAULT_LOOK_BEHIND, - minCovered: Duration? = null, maxDataRequest: Int = DEFAULT_MAX_DATA_REQUEST, ): Schedule? = try { contentResolver.query( @@ -47,7 +44,6 @@ fun Context.getRDSSchedule( StatusProviderContract.PROJECTION_STATUS, Schedule.ScheduleStatusFilter(rds).apply { setLookBehindInMs(lookBehind.inWholeMilliseconds) - setMinUsefulDurationCoveredInMs(minCovered?.inWholeMilliseconds) setMaxDataRequests(maxDataRequest) setIncludeCancelledTimestamps(includeCancelledTimestamps) }.let { it.toJSONStringStatic(it) }, diff --git a/src/main/java/org/mtransit/android/commons/provider/serviceupdate/GTFSRealTimeServiceAlertsProvider.kt b/src/main/java/org/mtransit/android/commons/provider/serviceupdate/GTFSRealTimeServiceAlertsProvider.kt index fc780383..738bfec4 100644 --- a/src/main/java/org/mtransit/android/commons/provider/serviceupdate/GTFSRealTimeServiceAlertsProvider.kt +++ b/src/main/java/org/mtransit/android/commons/provider/serviceupdate/GTFSRealTimeServiceAlertsProvider.kt @@ -46,7 +46,7 @@ object GTFSRealTimeServiceAlertsProvider { // trip IDs preferred for all result filtered correctly getCachedServiceUpdatesS(targetUUIDs.keys, tripIds)?.takeIf { it.isNotEmpty() } } ?: run { - // fall back to showing all w/o filtering trip IDs + // fallback to showing all w/o filtering trip IDs (main issue would be RDS UI showing other Direction alerts) getCachedServiceUpdatesS(targetUUIDs.keys) }?.let { addAll(it) diff --git a/src/main/java/org/mtransit/android/commons/provider/status/GTFSRealTimeTripUpdatesProvider.kt b/src/main/java/org/mtransit/android/commons/provider/status/GTFSRealTimeTripUpdatesProvider.kt index a2ed0669..99d85311 100644 --- a/src/main/java/org/mtransit/android/commons/provider/status/GTFSRealTimeTripUpdatesProvider.kt +++ b/src/main/java/org/mtransit/android/commons/provider/status/GTFSRealTimeTripUpdatesProvider.kt @@ -228,6 +228,7 @@ object GTFSRealTimeTripUpdatesProvider : MTLog.Loggable { } } + // fallback to generate a whole new schedule from Trip Updates (only if all STU contains time instead of delay) private fun GTFSRealTimeProvider.makeCachedStatusFromAgencyDataFallback( rds: RouteDirectionStop, filter: Schedule.ScheduleStatusFilter, diff --git a/src/main/java/org/mtransit/android/commons/provider/vehiclelocations/GTFSRealTimeVehiclePositionsProvider.kt b/src/main/java/org/mtransit/android/commons/provider/vehiclelocations/GTFSRealTimeVehiclePositionsProvider.kt index de3c0983..a7d72afd 100644 --- a/src/main/java/org/mtransit/android/commons/provider/vehiclelocations/GTFSRealTimeVehiclePositionsProvider.kt +++ b/src/main/java/org/mtransit/android/commons/provider/vehiclelocations/GTFSRealTimeVehiclePositionsProvider.kt @@ -103,7 +103,9 @@ object GTFSRealTimeVehiclePositionsProvider { tripIds?.let { // trip IDs preferred for all result filtered correctly getCachedVehicleLocationsS(targetUUIDs.keys, tripIds)?.takeIf { it.isNotEmpty() } } ?: primaryTargetUUID?.let { (providerTargetUUID, _) -> - // fall back to: ignore TRIP IDS (outdated?) and try using primary target UUID only (ex: Route-Direction IDs available) + // fallback to: ignore TRIP IDS (outdated?) and try using primary target UUID only + // - only works if Route (& Direction) provided + // -> can show vehicle in wrong direction getCachedVehicleLocationsS(providerTargetUUID) }?.let { addAll(it) From 439bf1185f12c01da08bce8a1a9d7a00a64bbcab Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mathieu=20M=C3=A9a?= Date: Thu, 2 Apr 2026 08:09:58 -0400 Subject: [PATCH 05/17] Update src/main/java/org/mtransit/android/commons/provider/status/GTFSRealTimeTripUpdatesProvider.kt Co-authored-by: gemini-code-assist[bot] <176961590+gemini-code-assist[bot]@users.noreply.github.com> --- .../commons/provider/status/GTFSRealTimeTripUpdatesProvider.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/java/org/mtransit/android/commons/provider/status/GTFSRealTimeTripUpdatesProvider.kt b/src/main/java/org/mtransit/android/commons/provider/status/GTFSRealTimeTripUpdatesProvider.kt index 99d85311..ab714a66 100644 --- a/src/main/java/org/mtransit/android/commons/provider/status/GTFSRealTimeTripUpdatesProvider.kt +++ b/src/main/java/org/mtransit/android/commons/provider/status/GTFSRealTimeTripUpdatesProvider.kt @@ -254,7 +254,7 @@ object GTFSRealTimeTripUpdatesProvider : MTLog.Loggable { return@filter true }.takeIf { it.isNotEmpty() } ?: return null - val notAllWithTime = rdTripUpdates.flatMap { it.optStopTimeUpdateList.orEmpty() }.any { it.departure.optTime == null } + val notAllWithTime = rdTripUpdates.flatMap { it.optStopTimeUpdateList.orEmpty() }.any { it.optDeparture?.optTime == null && it.optArrival?.optTime == null } if (notAllWithTime) return null val uuidSchedule = mutableMapOf() val sortedRDS = getRDS() From a1ebc9482fd1a5723ab110147c87f11e7eed8ef3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mathieu=20M=C3=A9a?= Date: Thu, 2 Apr 2026 08:10:19 -0400 Subject: [PATCH 06/17] Update src/main/java/org/mtransit/android/commons/provider/status/GTFSRealTimeTripUpdatesProvider.kt Co-authored-by: gemini-code-assist[bot] <176961590+gemini-code-assist[bot]@users.noreply.github.com> --- .../commons/provider/status/GTFSRealTimeTripUpdatesProvider.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/java/org/mtransit/android/commons/provider/status/GTFSRealTimeTripUpdatesProvider.kt b/src/main/java/org/mtransit/android/commons/provider/status/GTFSRealTimeTripUpdatesProvider.kt index ab714a66..ea567719 100644 --- a/src/main/java/org/mtransit/android/commons/provider/status/GTFSRealTimeTripUpdatesProvider.kt +++ b/src/main/java/org/mtransit/android/commons/provider/status/GTFSRealTimeTripUpdatesProvider.kt @@ -266,7 +266,7 @@ object GTFSRealTimeTripUpdatesProvider : MTLog.Loggable { gTripUpdates.forEach { gTripUpdate -> gTripUpdate.optStopTimeUpdateList?.forEach { gStopTimeUpdate -> val stuStopIdHash = gStopTimeUpdate.optStopId?.let { parseStopId(it) } ?: return@forEach - val departureMs = gStopTimeUpdate.optDeparture?.optTimeMs ?: return@forEach + val departureMs = gStopTimeUpdate.optDeparture?.optTimeMs ?: gStopTimeUpdate.optArrival?.optTimeMs ?: return@forEach val targetUUID = sortedRDS?.singleOrNull { rds -> if (rds.route.originalIdHash.toString() != tdRouteIdHasString) return@singleOrNull false if (!rds.stop.isSameOriginalId(stuStopIdHash)) return@singleOrNull false From 6d356062310d04464fc6fbcb05c75d122523652e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mathieu=20M=C3=A9a?= Date: Thu, 2 Apr 2026 11:00:46 -0400 Subject: [PATCH 07/17] wip --- .../java/org/mtransit/android/MtLogExt.kt | 14 +- .../android/commons/data/Schedule.java | 15 +- .../android/commons/data/ScheduleExt.kt | 69 +++++- .../status/GTFSRealTimeTripUpdatesProvider.kt | 220 +++++++++++------- 4 files changed, 216 insertions(+), 102 deletions(-) diff --git a/src/main/java/org/mtransit/android/MtLogExt.kt b/src/main/java/org/mtransit/android/MtLogExt.kt index 67daa359..4230a68b 100644 --- a/src/main/java/org/mtransit/android/MtLogExt.kt +++ b/src/main/java/org/mtransit/android/MtLogExt.kt @@ -1,15 +1,23 @@ +@file:Suppress("unused") + package org.mtransit.android import org.mtransit.android.commons.MTLog +import org.mtransit.android.commons.toMillis import java.util.Calendar import java.util.Date +import kotlin.time.Duration +import kotlin.time.Instant -fun Long?.toDateTimeLog() = MTLog.formatDateTime(this) +fun Long?.toDurationLog() = MTLog.formatDuration(this) +fun Date?.toDurationLog() = this?.time.toDurationLog() +fun Calendar?.toDurationLog() = this?.timeInMillis.toDurationLog() +fun Duration?.toDurationLog() = this?.inWholeMilliseconds.toDateTimeLog() +fun Long?.toDateTimeLog() = MTLog.formatDateTime(this) fun Date?.toDateTimeLog() = this?.time.toDateTimeLog() - -@Suppress("unused") fun Calendar?.toDateTimeLog() = this?.time.toDateTimeLog() +fun Instant?.toDateTimeLog() = this?.toMillis().toDateTimeLog() @Deprecated("Use toDateTimeLog() instead", ReplaceWith("this.toDateTimeLog()")) fun Long?.formatDateTime() = this.toDateTimeLog() diff --git a/src/main/java/org/mtransit/android/commons/data/Schedule.java b/src/main/java/org/mtransit/android/commons/data/Schedule.java index 1b31596d..ec550dc3 100644 --- a/src/main/java/org/mtransit/android/commons/data/Schedule.java +++ b/src/main/java/org/mtransit/android/commons/data/Schedule.java @@ -100,13 +100,7 @@ public void setProviderPrecisionInMs(long providerPrecisionInMs) { @NonNull @Override public String toString() { - return Schedule.class.getSimpleName() + "{" + - "timestamps=" + timestamps + - ", providerPrecisionInMs=" + providerPrecisionInMs + - ", usefulUntilInMs=" + usefulUntilInMs + - ", noPickup=" + noPickup + - ", frequencies=" + frequencies + - '}'; + return ScheduleExtKt.toStringK(this); } @Nullable @@ -230,9 +224,7 @@ public int getFrequenciesCount() { } public void addTimestampWithoutSort(@Nullable Timestamp newTimestamp) { - if (newTimestamp == null) { - return; - } + if (newTimestamp == null) return; this.timestamps.add(newTimestamp); } @@ -508,6 +500,9 @@ public void setArrivalT(long arrivalT) { } public void setArrivalDiffMs(@Nullable Long arrivalDiffMs) { + if (arrivalDiffMs != null && arrivalDiffMs == 0L) { + arrivalDiffMs = null; + } this.arrivalDiffMs = arrivalDiffMs; } diff --git a/src/main/java/org/mtransit/android/commons/data/ScheduleExt.kt b/src/main/java/org/mtransit/android/commons/data/ScheduleExt.kt index fc713c9f..319b4d64 100644 --- a/src/main/java/org/mtransit/android/commons/data/ScheduleExt.kt +++ b/src/main/java/org/mtransit/android/commons/data/ScheduleExt.kt @@ -6,6 +6,7 @@ import org.mtransit.android.commons.millisToInstant import org.mtransit.android.commons.roundToNearest import org.mtransit.android.commons.toMillis import org.mtransit.android.toDateTimeLog +import org.mtransit.android.toDurationLog import kotlin.time.Duration import kotlin.time.Duration.Companion.milliseconds import kotlin.time.Duration.Companion.seconds @@ -33,16 +34,35 @@ fun makeSchedule( noData, ) -fun Schedule.toNoData() = Schedule( - id, - targetUUID, - lastUpdateInMs, - validityInMs, - readFromSourceAtInMs, - providerPrecisionInMs, - isNoPickup, - sourceLabel, - true // NO DATA +fun RouteDirectionStop.makeSchedule( + lastUpdateInMs: Long, + validityInMs: Long, + readFromSourceAtInMs: Long, + providerPrecisionInMs: Long, + sourceLabel: String, + noData: Boolean, +) = makeSchedule( + targetUUID = uuid, + lastUpdateInMs = lastUpdateInMs, + validityInMs = validityInMs, + readFromSourceAtInMs = readFromSourceAtInMs, + providerPrecisionInMs = providerPrecisionInMs, + sourceLabel = sourceLabel, + noData = noData +).apply { + isNoPickup = this@makeSchedule.isNoPickup +} + +fun Schedule.toNoData() = makeSchedule( + id = id, + targetUUID = targetUUID, + lastUpdateInMs = lastUpdateInMs, + validityInMs = validityInMs, + readFromSourceAtInMs = readFromSourceAtInMs, + providerPrecisionInMs = providerPrecisionInMs, + isNoPickup = isNoPickup, + sourceLabel = sourceLabel, + noData = true // NO DATA ) val Schedule.providerPrecision get() = providerPrecisionInMs.milliseconds @@ -150,6 +170,35 @@ fun Schedule.Timestamp.updateArrivalForRealTime(newArrival: Instant) { @Suppress("unused") val Schedule.hasRealTime get() = this.timestamps.any { it.isRealTime } +fun Schedule.toStringK() = buildString { + append("S{") + append("uuid:").append(targetUUID) + append(",") + append("proPre:").append(providerPrecisionInMs.toDurationLog()) + append(",") + append("useUtl:").append(usefulUntilInMs.toDateTimeLog()) + append(",") + if (isNoPickup) { + append("noPickup") + append(",") + } + timestamps.takeIf { it.isNotEmpty() }?.let { timestamps -> + append("t[").append(timestamps.size).append("]") + if (Constants.DEBUG) { + append(timestamps.joinToString(separator = ",", prefix = ":{", postfix = "}") { it.toStringShort() }) + } + append(",") + } + frequencies.takeIf { it.isNotEmpty() }?.let { frequencies -> + append("f[").append(frequencies.size).append("]") + if (Constants.DEBUG) { + append(frequencies.joinToString(separator = ",", prefix = ":{", postfix = "}") { it.toString() }) + } + append(",") + } + append("}") +} + @Suppress("unused") fun Schedule.Timestamp.toStringShort() = buildString { append("T{") diff --git a/src/main/java/org/mtransit/android/commons/provider/status/GTFSRealTimeTripUpdatesProvider.kt b/src/main/java/org/mtransit/android/commons/provider/status/GTFSRealTimeTripUpdatesProvider.kt index ea567719..cdd720cd 100644 --- a/src/main/java/org/mtransit/android/commons/provider/status/GTFSRealTimeTripUpdatesProvider.kt +++ b/src/main/java/org/mtransit/android/commons/provider/status/GTFSRealTimeTripUpdatesProvider.kt @@ -20,7 +20,9 @@ import org.mtransit.android.commons.provider.gtfs.GtfsRealTimeStorage import org.mtransit.android.commons.provider.gtfs.GtfsRealtimeExt.optArrival import org.mtransit.android.commons.provider.gtfs.GtfsRealtimeExt.optDeparture import org.mtransit.android.commons.provider.gtfs.GtfsRealtimeExt.optDirectionId -import org.mtransit.android.commons.provider.gtfs.GtfsRealtimeExt.optRouteId +import org.mtransit.android.commons.provider.gtfs.GtfsRealtimeExt.optPickupType +import org.mtransit.android.commons.provider.gtfs.GtfsRealtimeExt.optScheduleRelationship +import org.mtransit.android.commons.provider.gtfs.GtfsRealtimeExt.optStopHeadsign import org.mtransit.android.commons.provider.gtfs.GtfsRealtimeExt.optStopId import org.mtransit.android.commons.provider.gtfs.GtfsRealtimeExt.optStopTimeUpdateList import org.mtransit.android.commons.provider.gtfs.GtfsRealtimeExt.optTime @@ -50,7 +52,11 @@ import kotlin.time.Duration.Companion.hours import kotlin.time.Duration.Companion.minutes import kotlin.time.Duration.Companion.seconds import com.google.transit.realtime.GtfsRealtime.FeedMessage as GFeedMessage +import com.google.transit.realtime.GtfsRealtime.TripDescriptor.ScheduleRelationship as GTDScheduleRelationship import com.google.transit.realtime.GtfsRealtime.TripUpdate as GTripUpdate +import com.google.transit.realtime.GtfsRealtime.TripUpdate.StopTimeUpdate as GTUStopTimeUpdate +import com.google.transit.realtime.GtfsRealtime.TripUpdate.StopTimeUpdate.StopTimeProperties.DropOffPickupType as GTUSTUSTPDropOffPickupType +import com.google.transit.realtime.GtfsRealtime.TripUpdate.StopTimeUpdate.ScheduleRelationship as GTUSTUScheduleRelationship object GTFSRealTimeTripUpdatesProvider : MTLog.Loggable { @@ -184,43 +190,7 @@ object GTFSRealTimeTripUpdatesProvider : MTLog.Loggable { ?.associateBy { it.targetUUID } uuidSchedule ?: return null processRDTripUpdates(rdTripUpdates, uuidSchedule, sortedRDS, filter.isIncludeCancelledTimestampsOrDefault) - val tripsWithRealTime = uuidSchedule.values - .asSequence() - .mapNotNull { schedule -> schedule.timestamps.takeIf { it.isNotEmpty() } }.flatten() - .filter { it.isRealTime } - .map { it.tripId } - .toSet() // distinct - uuidSchedule.forEach { (_, schedule) -> - val now = TimeUtilsK.currentInstant() - if (!schedule.timestamps.any { it.isRealTime || (it.tripId in tripsWithRealTime && it.departure < now) }) { - cacheStatus(schedule.toNoData()) // avoid re-run - return@forEach - } - var oldestDateForRealTime = now - 1.minutes - var maxFutureDateForRealTime = now + 12.hours - val (past, future) = schedule.timestamps.partition { it.departure < now } - oldestDateForRealTime = past.filter { it.isRealTime }.minOfOrNull { it.arrival } // all real-time - ?: oldestDateForRealTime - maxFutureDateForRealTime = future.take(10).maxOfOrNull { it.departure } // keep firsts 10 - ?.takeIf { it > maxFutureDateForRealTime } - ?: maxFutureDateForRealTime - maxFutureDateForRealTime = future.filter { it.isRealTime }.maxOfOrNull { it.departure } // all real-time - ?.takeIf { it > maxFutureDateForRealTime } - ?: maxFutureDateForRealTime - schedule.timestamps - .filterNot { - it.isRealTime || oldestDateForRealTime < it.arrival && it.departure < maxFutureDateForRealTime - } - .forEach { timestamp -> - schedule.removeTimestamp(timestamp) - } - schedule.sourceLabel = sourceLabel - schedule.lastUpdateInMs = readFromSourceMs - schedule.readFromSourceAtInMs = readFromSourceMs - schedule.providerPrecisionInMs = PROVIDER_PRECISION_IN_MS - schedule.validityInMs = TRIP_UPDATE_VALIDITY_IN_MS - cacheStatus(schedule) - } + cacheRealTimeSchedules(uuidSchedule.values, sourceLabel, readFromSourceMs, readFromSourceMs) return getCachedStatusS(filter.targetUUID, tripIds) } catch (e: Exception) { MTLog.w(this, e, "makeCachedStatusFromAgencyData() > error!") @@ -228,85 +198,177 @@ object GTFSRealTimeTripUpdatesProvider : MTLog.Loggable { } } + private val OLDEST_FOR_REAL_TIME = 1.minutes + private val MAX_FUTURE_FOR_REAL_TIME = 12.hours + + private fun GTFSRealTimeProvider.cacheRealTimeSchedules( + scheduleList: Collection, + sourceLabel: String, + lastUpdateInMs: Long, + readFromSourceMs: Long, + ) { + val tripsWithRealTime = scheduleList + .asSequence() + .mapNotNull { schedule -> schedule.timestamps.takeIf { it.isNotEmpty() } }.flatten() + .filter { it.isRealTime } + .map { it.tripId } + .toSet() // distinct + scheduleList.forEach { schedule -> + schedule.sourceLabel = sourceLabel + schedule.lastUpdateInMs = lastUpdateInMs + schedule.readFromSourceAtInMs = readFromSourceMs + schedule.providerPrecisionInMs = PROVIDER_PRECISION_IN_MS + schedule.validityInMs = TRIP_UPDATE_VALIDITY_IN_MS + val now = TimeUtilsK.currentInstant() + if (schedule.timestamps.none { it.isRealTime || (it.tripId in tripsWithRealTime && it.departure < now) }) { + cacheStatus(schedule.toNoData()) // avoid re-run + return@forEach + } + var oldestDateForRealTime = now - OLDEST_FOR_REAL_TIME + var maxFutureDateForRealTime = now + MAX_FUTURE_FOR_REAL_TIME + val (past, future) = schedule.timestamps.partition { it.departure < now } + oldestDateForRealTime = past.filter { it.isRealTime }.minOfOrNull { it.arrival } // all real-time + ?: oldestDateForRealTime + maxFutureDateForRealTime = future.take(10).maxOfOrNull { it.departure } // keep firsts 10 + ?.takeIf { it > maxFutureDateForRealTime } + ?: maxFutureDateForRealTime + maxFutureDateForRealTime = future.filter { it.isRealTime }.maxOfOrNull { it.departure } // all real-time + ?.takeIf { it > maxFutureDateForRealTime } + ?: maxFutureDateForRealTime + schedule.timestamps + .filterNot { + it.isRealTime || oldestDateForRealTime < it.arrival && it.departure < maxFutureDateForRealTime + } + .forEach { timestamp -> + schedule.removeTimestamp(timestamp) + } + cacheStatus(schedule) + } + } + // fallback to generate a whole new schedule from Trip Updates (only if all STU contains time instead of delay) private fun GTFSRealTimeProvider.makeCachedStatusFromAgencyDataFallback( rds: RouteDirectionStop, + targetRouteIdHash: String = rds.route.originalIdHash.toString(), + targetDirectionOriginalId: Int? = rds.direction.originalDirectionIdOrNull, filter: Schedule.ScheduleStatusFilter, tripIds: List, readFromSourceMs: Long, sourceLabel: String, gTripUpdates: List, + includeCancelledTimestamps: Boolean, getRDS: () -> List?, ): POIStatus? { val rdTripUpdates = gTripUpdates .filter { gTripUpdate -> val td = gTripUpdate.optTrip ?: return@filter false parseRouteId(td)?.let { routeIdHash -> - if (routeIdHash != rds.route.originalIdHash.toString()) { + if (routeIdHash != targetRouteIdHash) { return@filter false } } td.optDirectionId?.takeIf { !ignoreDirection }?.let { directionId -> - if (directionId != rds.direction.originalDirectionIdOrNull) { + if (directionId != targetDirectionOriginalId) { return@filter false } } + if (td.optScheduleRelationship == GTDScheduleRelationship.DELETED) return@filter false + if (!includeCancelledTimestamps && td.optScheduleRelationship == GTDScheduleRelationship.CANCELED) return@filter false return@filter true }.takeIf { it.isNotEmpty() } ?: return null - val notAllWithTime = rdTripUpdates.flatMap { it.optStopTimeUpdateList.orEmpty() }.any { it.optDeparture?.optTime == null && it.optArrival?.optTime == null } + val notAllWithTime = + rdTripUpdates.flatMap { it.optStopTimeUpdateList.orEmpty() }.any { it.optDeparture?.optTime == null && it.optArrival?.optTime == null } if (notAllWithTime) return null val uuidSchedule = mutableMapOf() val sortedRDS = getRDS() - rdTripUpdates.groupBy { it.optTrip }.forEach { (gTripDescriptor, gTripUpdates) -> - gTripDescriptor ?: return@forEach - val tdRouteIdHasString = gTripDescriptor.optRouteId?.let { parseRouteId(it) } ?: return@forEach - val tdDirectionId = gTripDescriptor.optDirectionId - if (!ignoreDirection && tdDirectionId == null) return@forEach - gTripUpdates.forEach { gTripUpdate -> - gTripUpdate.optStopTimeUpdateList?.forEach { gStopTimeUpdate -> - val stuStopIdHash = gStopTimeUpdate.optStopId?.let { parseStopId(it) } ?: return@forEach - val departureMs = gStopTimeUpdate.optDeparture?.optTimeMs ?: gStopTimeUpdate.optArrival?.optTimeMs ?: return@forEach - val targetUUID = sortedRDS?.singleOrNull { rds -> - if (rds.route.originalIdHash.toString() != tdRouteIdHasString) return@singleOrNull false - if (!rds.stop.isSameOriginalId(stuStopIdHash)) return@singleOrNull false - if (!ignoreDirection && tdDirectionId != rds.direction.originalDirectionIdOrNull) return@singleOrNull false - return@singleOrNull true - }?.uuid ?: return@forEach - val rdsSchedule = uuidSchedule.getOrPut(targetUUID) { - makeSchedule( - targetUUID = targetUUID, - lastUpdateInMs = readFromSourceMs, - validityInMs = TRIP_UPDATE_VALIDITY_IN_MS, - readFromSourceAtInMs = readFromSourceMs, - providerPrecisionInMs = PROVIDER_PRECISION_IN_MS, - sourceLabel = sourceLabel, - noData = false - ) - } - rdsSchedule.addTimestampWithoutSort( - Schedule.Timestamp( - departureMs, - timeZone - ).apply { - realTime = true - gStopTimeUpdate.optArrival?.optTimeMs?.let { this.arrivalT = it } - gStopTimeUpdate?.stopTimeProperties?.let { stp -> - stp.stopHeadsign?.takeIf { it.isNotBlank() }?.let { this.setHeadsign(Direction.HEADSIGN_TYPE_STRING, it) } - } - } + rdTripUpdates.forEach { gTripUpdate -> + gTripUpdate.optStopTimeUpdateList?.forEach { gStopTimeUpdate -> + val stuStopIdHash = gStopTimeUpdate.optStopId?.let { parseStopId(it) } ?: return@forEach + val targetRDS = sortedRDS?.singleOrNull { it.stop.isSameOriginalId(stuStopIdHash) } + ?: return@forEach + val rdsSchedule = uuidSchedule.getOrPut(targetRDS.uuid) { + targetRDS.makeSchedule( + lastUpdateInMs = readFromSourceMs, + validityInMs = TRIP_UPDATE_VALIDITY_IN_MS, + readFromSourceAtInMs = readFromSourceMs, + providerPrecisionInMs = PROVIDER_PRECISION_IN_MS, + sourceLabel = sourceLabel, + noData = false, ) } + rdsSchedule.addTimestampWithoutSort( + gStopTimeUpdate.toTimestamp( + provider = this, + rds = targetRDS, + gTripUpdate = gTripUpdate, + includeCancelledTimestamps = includeCancelledTimestamps, + ) + ) } } uuidSchedule.takeIf { it.isNotEmpty() } ?: return null uuidSchedule.values.forEach { schedule -> schedule.sortTimestamps() - cacheStatus(schedule) } + sortedRDS?.filter { uuidSchedule[it.uuid] == null }?.forEach { rds -> + uuidSchedule[rds.uuid] = rds.makeSchedule( + lastUpdateInMs = readFromSourceMs, + validityInMs = TRIP_UPDATE_VALIDITY_IN_MS, + readFromSourceAtInMs = readFromSourceMs, + providerPrecisionInMs = PROVIDER_PRECISION_IN_MS, + sourceLabel = sourceLabel, + noData = false, + ) + } + cacheRealTimeSchedules(uuidSchedule.values, sourceLabel, readFromSourceMs, readFromSourceMs) return getCachedStatusS(filter.targetUUID, tripIds) } + fun GTUStopTimeUpdate.toTimestamp( + provider: GTFSRealTimeProvider, + rds: RouteDirectionStop, + gTripUpdate: GTripUpdate, + includeCancelledTimestamps: Boolean, + ): Schedule.Timestamp? { + val arrival = optArrival?.takeIf { scheduleRelationship != GTUSTUScheduleRelationship.NO_DATA } + val departure = optDeparture?.takeIf { scheduleRelationship != GTUSTUScheduleRelationship.NO_DATA } + val departureMs = departure?.optTimeMs + ?: arrival?.optTimeMs + ?: return null // no time for timestamp + if (!includeCancelledTimestamps + && (scheduleRelationship == GTUSTUScheduleRelationship.SKIPPED + || gTripUpdate.optTrip?.optScheduleRelationship == GTDScheduleRelationship.CANCELED) + ) { + return null + } + return Schedule.Timestamp( + departureMs, + provider.timeZone + ).apply { + if (Constants.DEBUG) { + tripId = gTripUpdate.optTrip?.tripId // this trip ID does NOT match static data! + } + realTime = true + arrival?.optTimeMs?.let { this.arrivalT = it } + if (scheduleRelationship == GTUSTUScheduleRelationship.SKIPPED) { + cancelled = true + } else if (gTripUpdate.optTrip?.optScheduleRelationship == GTDScheduleRelationship.CANCELED) { + cancelled = true + } + stopTimeProperties?.let { stp -> + stp.optStopHeadsign?.takeIf { it.isNotBlank() }?.let { + this.setHeadsign(Direction.HEADSIGN_TYPE_STRING, it) + } + if (stp.optPickupType == GTUSTUSTPDropOffPickupType.NONE) { + this.setHeadsign(Direction.HEADSIGN_TYPE_NO_PICKUP, null) + } else if (rds.isNoPickup) { + this.setHeadsign(Direction.HEADSIGN_TYPE_NO_PICKUP, null) + } + } + } + } + @JvmStatic fun GTFSRealTimeProvider.getNew(statusFilter: StatusProviderContract.Filter): POIStatus? { val filter = statusFilter as? Schedule.ScheduleStatusFilter ?: run { From a9c476efb4d7ca9a37f6caca54dc7dcf9cadc46b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mathieu=20M=C3=A9a?= Date: Thu, 2 Apr 2026 11:31:35 -0400 Subject: [PATCH 08/17] missing --- .../commons/provider/status/GTFSRealTimeTripUpdatesProvider.kt | 1 + 1 file changed, 1 insertion(+) diff --git a/src/main/java/org/mtransit/android/commons/provider/status/GTFSRealTimeTripUpdatesProvider.kt b/src/main/java/org/mtransit/android/commons/provider/status/GTFSRealTimeTripUpdatesProvider.kt index cdd720cd..e0416be8 100644 --- a/src/main/java/org/mtransit/android/commons/provider/status/GTFSRealTimeTripUpdatesProvider.kt +++ b/src/main/java/org/mtransit/android/commons/provider/status/GTFSRealTimeTripUpdatesProvider.kt @@ -172,6 +172,7 @@ object GTFSRealTimeTripUpdatesProvider : MTLog.Loggable { readFromSourceMs = readFromSourceMs, sourceLabel = sourceLabel, gTripUpdates = gTripUpdates, + includeCancelledTimestamps = filter.isIncludeCancelledTimestampsOrDefault, getRDS = { context.getRDS(rds.authority, routeId, directionId) } ) if (Constants.DEBUG) { From cd44c35e4f8c4a30285988387d51e70b509227ac Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mathieu=20M=C3=A9a?= Date: Thu, 2 Apr 2026 14:27:06 -0400 Subject: [PATCH 09/17] wip --- .../status/GTFSRealTimeTripUpdatesProvider.kt | 15 ++++++++------- 1 file changed, 8 insertions(+), 7 deletions(-) diff --git a/src/main/java/org/mtransit/android/commons/provider/status/GTFSRealTimeTripUpdatesProvider.kt b/src/main/java/org/mtransit/android/commons/provider/status/GTFSRealTimeTripUpdatesProvider.kt index e0416be8..0ec320f8 100644 --- a/src/main/java/org/mtransit/android/commons/provider/status/GTFSRealTimeTripUpdatesProvider.kt +++ b/src/main/java/org/mtransit/android/commons/provider/status/GTFSRealTimeTripUpdatesProvider.kt @@ -55,8 +55,8 @@ import com.google.transit.realtime.GtfsRealtime.FeedMessage as GFeedMessage import com.google.transit.realtime.GtfsRealtime.TripDescriptor.ScheduleRelationship as GTDScheduleRelationship import com.google.transit.realtime.GtfsRealtime.TripUpdate as GTripUpdate import com.google.transit.realtime.GtfsRealtime.TripUpdate.StopTimeUpdate as GTUStopTimeUpdate -import com.google.transit.realtime.GtfsRealtime.TripUpdate.StopTimeUpdate.StopTimeProperties.DropOffPickupType as GTUSTUSTPDropOffPickupType import com.google.transit.realtime.GtfsRealtime.TripUpdate.StopTimeUpdate.ScheduleRelationship as GTUSTUScheduleRelationship +import com.google.transit.realtime.GtfsRealtime.TripUpdate.StopTimeUpdate.StopTimeProperties.DropOffPickupType as GTUSTUSTPDropOffPickupType object GTFSRealTimeTripUpdatesProvider : MTLog.Loggable { @@ -207,13 +207,13 @@ object GTFSRealTimeTripUpdatesProvider : MTLog.Loggable { sourceLabel: String, lastUpdateInMs: Long, readFromSourceMs: Long, - ) { - val tripsWithRealTime = scheduleList + tripsWithRealTime: Set = scheduleList .asSequence() .mapNotNull { schedule -> schedule.timestamps.takeIf { it.isNotEmpty() } }.flatten() .filter { it.isRealTime } - .map { it.tripId } + .mapNotNull { it.tripId } .toSet() // distinct + ) { scheduleList.forEach { schedule -> schedule.sourceLabel = sourceLabel schedule.lastUpdateInMs = lastUpdateInMs @@ -221,7 +221,7 @@ object GTFSRealTimeTripUpdatesProvider : MTLog.Loggable { schedule.providerPrecisionInMs = PROVIDER_PRECISION_IN_MS schedule.validityInMs = TRIP_UPDATE_VALIDITY_IN_MS val now = TimeUtilsK.currentInstant() - if (schedule.timestamps.none { it.isRealTime || (it.tripId in tripsWithRealTime && it.departure < now) }) { + if (!schedule.timestamps.any { it.isRealTime || (it.tripId in tripsWithRealTime && it.departure < now) }) { cacheStatus(schedule.toNoData()) // avoid re-run return@forEach } @@ -237,8 +237,9 @@ object GTFSRealTimeTripUpdatesProvider : MTLog.Loggable { ?.takeIf { it > maxFutureDateForRealTime } ?: maxFutureDateForRealTime schedule.timestamps - .filterNot { - it.isRealTime || oldestDateForRealTime < it.arrival && it.departure < maxFutureDateForRealTime + .filter { timestamp -> + !timestamp.isRealTime + && (timestamp.arrival <= oldestDateForRealTime || maxFutureDateForRealTime <= timestamp.departure) } .forEach { timestamp -> schedule.removeTimestamp(timestamp) From 8244efe87ca5568375ece180af417a8b09dc8d28 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mathieu=20M=C3=A9a?= Date: Thu, 2 Apr 2026 14:39:39 -0400 Subject: [PATCH 10/17] cleanup --- .../status/GTFSRealTimeTripUpdatesProvider.kt | 68 ++++++++----------- 1 file changed, 29 insertions(+), 39 deletions(-) diff --git a/src/main/java/org/mtransit/android/commons/provider/status/GTFSRealTimeTripUpdatesProvider.kt b/src/main/java/org/mtransit/android/commons/provider/status/GTFSRealTimeTripUpdatesProvider.kt index 0ec320f8..96448bf2 100644 --- a/src/main/java/org/mtransit/android/commons/provider/status/GTFSRealTimeTripUpdatesProvider.kt +++ b/src/main/java/org/mtransit/android/commons/provider/status/GTFSRealTimeTripUpdatesProvider.kt @@ -9,6 +9,7 @@ import org.mtransit.android.commons.TimeUtils import org.mtransit.android.commons.TimeUtilsK import org.mtransit.android.commons.data.Direction import org.mtransit.android.commons.data.POIStatus +import org.mtransit.android.commons.data.RouteDirection import org.mtransit.android.commons.data.RouteDirectionStop import org.mtransit.android.commons.data.Schedule import org.mtransit.android.commons.data.arrival @@ -97,7 +98,7 @@ object GTFSRealTimeTripUpdatesProvider : MTLog.Loggable { @JvmStatic fun GTFSRealTimeProvider.getCached(statusFilter: StatusProviderContract.Filter): POIStatus? { val filter = statusFilter as? Schedule.ScheduleStatusFilter ?: run { - MTLog.w(this@GTFSRealTimeTripUpdatesProvider, "getCached() > Can't find new schedule without schedule filter!") + MTLog.w(LOG_TAG, "getCached() > Can't find new schedule without schedule filter!") return null } val tripIds = filter.targetAuthority.let { targetAuthority -> @@ -138,10 +139,10 @@ object GTFSRealTimeTripUpdatesProvider : MTLog.Loggable { GTFSRealTimeProvider.getAgencyTripUpdatesUrlString(context, "T") ) try { - val rds = filter.routeDirectionStop - val targetAuthority = rds.authority - val routeId = rds.route.id - val directionId = rds.direction.id + val routeDirection = filter.routeDirectionStop.let { RouteDirection(it.route, it.direction) } + val targetAuthority = routeDirection.authority + val route = routeDirection.route + val direction = routeDirection.direction val gFeedMessage = GFeedMessage.parseFrom(gtfsRealTimeTripUpdateFile.inputStream()) val gTripUpdates = gFeedMessage.entityList.toTripUpdates() val rdTripUpdates = gTripUpdates @@ -149,42 +150,33 @@ object GTFSRealTimeTripUpdatesProvider : MTLog.Loggable { gTripUpdate.optTrip?.let { it to gTripUpdate } }.filter { (td, _) -> parseTripId(td)?.let { tripId -> - if (tripId !in tripIds) { - return@filter false - } + if (tripId !in tripIds) return@filter false } parseRouteId(td)?.let { routeIdHash -> - if (routeIdHash != rds.route.originalIdHash.toString()) { - return@filter false - } + if (routeIdHash != route.originalIdHash.toString()) return@filter false } td.optDirectionId?.takeIf { !ignoreDirection }?.let { directionId -> - if (directionId != rds.direction.originalDirectionIdOrNull) { - return@filter false - } + if (directionId != direction.originalDirectionIdOrNull) return@filter false } return@filter true }.takeIf { it.isNotEmpty() } rdTripUpdates ?: return makeCachedStatusFromAgencyDataFallback( - rds = rds, + targetRouteDirection = routeDirection, filter = filter, tripIds = tripIds, readFromSourceMs = readFromSourceMs, sourceLabel = sourceLabel, gTripUpdates = gTripUpdates, includeCancelledTimestamps = filter.isIncludeCancelledTimestampsOrDefault, - getRDS = { context.getRDS(rds.authority, routeId, directionId) } + getRDS = { context.getRDS(targetAuthority, route.id, direction.id) } ) if (Constants.DEBUG) { - MTLog.d( - this@GTFSRealTimeTripUpdatesProvider, - "makeCachedStatusFromAgencyData() > GTFS {R:'${rds.route.shortestName}'|D:${rds.direction.headsignValue}} [${gTripUpdates.size}]: " - ) + MTLog.d(LOG_TAG, "makeCachedStatusFromAgencyData() > GTFS {R:'${route.shortestName}'|D:${direction.headsignValue}} [${gTripUpdates.size}]: ") rdTripUpdates.forEach { (_, gTripUpdate) -> - MTLog.d(this@GTFSRealTimeTripUpdatesProvider, "makeCachedStatusFromAgencyData() > - GTFS ${gTripUpdate.toStringExt()}.") + MTLog.d(LOG_TAG, "makeCachedStatusFromAgencyData() > - GTFS ${gTripUpdate.toStringExt()}.") } } - val sortedRDS = context.getRDS(rds.authority, routeId, directionId) + val sortedRDS = context.getRDS(targetAuthority, route.id, direction.id) sortedRDS ?: return null val uuidSchedule = context.getRDSSchedule(targetAuthority, sortedRDS, filter.isIncludeCancelledTimestampsOrDefault) .takeIf { it.isNotEmpty() } @@ -236,6 +228,7 @@ object GTFSRealTimeTripUpdatesProvider : MTLog.Loggable { maxFutureDateForRealTime = future.filter { it.isRealTime }.maxOfOrNull { it.departure } // all real-time ?.takeIf { it > maxFutureDateForRealTime } ?: maxFutureDateForRealTime + // remove timestamps that are not real-time & outside of min/max date for real-time schedule.timestamps .filter { timestamp -> !timestamp.isRealTime @@ -250,9 +243,9 @@ object GTFSRealTimeTripUpdatesProvider : MTLog.Loggable { // fallback to generate a whole new schedule from Trip Updates (only if all STU contains time instead of delay) private fun GTFSRealTimeProvider.makeCachedStatusFromAgencyDataFallback( - rds: RouteDirectionStop, - targetRouteIdHash: String = rds.route.originalIdHash.toString(), - targetDirectionOriginalId: Int? = rds.direction.originalDirectionIdOrNull, + targetRouteDirection: RouteDirection, + targetRouteIdHash: String = targetRouteDirection.route.originalIdHash.toString(), + targetDirectionOriginalId: Int? = targetRouteDirection.direction.originalDirectionIdOrNull, filter: Schedule.ScheduleStatusFilter, tripIds: List, readFromSourceMs: Long, @@ -374,7 +367,7 @@ object GTFSRealTimeTripUpdatesProvider : MTLog.Loggable { @JvmStatic fun GTFSRealTimeProvider.getNew(statusFilter: StatusProviderContract.Filter): POIStatus? { val filter = statusFilter as? Schedule.ScheduleStatusFilter ?: run { - MTLog.w(this@GTFSRealTimeTripUpdatesProvider, "getNew() > Can't find new schedule without schedule filter!") + MTLog.w(LOG_TAG, "getNew() > Can't find new schedule without schedule filter!") return null } updateAgencyDataIfRequired(filter.isInFocusOrDefault) @@ -453,47 +446,44 @@ object GTFSRealTimeTripUpdatesProvider : MTLog.Loggable { if (Constants.DEBUG && PRINT_ALL_LOADED_TRIP_UPDATES) { val gFeedMessage = GFeedMessage.parseFrom(responseBodyByes) val gTripUpdates = gFeedMessage.entityList.toTripUpdates() - MTLog.d(this@GTFSRealTimeTripUpdatesProvider, "loadAgencyDataFromWWW() > GTFS trip updates[${gTripUpdates.size}]: ") + MTLog.d(LOG_TAG, "loadAgencyDataFromWWW() > GTFS trip updates[${gTripUpdates.size}]: ") gTripUpdates.sortTripUpdates(TimeUtils.currentTimeMillis()).forEach { gTripUpdate -> - MTLog.d(this@GTFSRealTimeTripUpdatesProvider, "loadAgencyDataFromWWW() > - GTFS ${gTripUpdate.toStringExt()}") + MTLog.d(LOG_TAG, "loadAgencyDataFromWWW() > - GTFS ${gTripUpdate.toStringExt()}") } } } catch (e: IOException) { - MTLog.w(this@GTFSRealTimeTripUpdatesProvider, e, "loadAgencyDataFromWWW() > error while saving GTFS RT Trip Updates data!") + MTLog.w(LOG_TAG, e, "loadAgencyDataFromWWW() > error while saving GTFS RT Trip Updates data!") } } catch (e: Exception) { - MTLog.w(this@GTFSRealTimeTripUpdatesProvider, e, "loadAgencyDataFromWWW() > error while parsing GTFS Real Time data!") + MTLog.w(LOG_TAG, e, "loadAgencyDataFromWWW() > error while parsing GTFS Real Time data!") } return true } else -> { - MTLog.w( - this@GTFSRealTimeTripUpdatesProvider, - "ERROR: HTTP URL-Connection Response Code ${response.code} (Message: ${response.message})" - ) + MTLog.w(LOG_TAG, "ERROR: HTTP URL-Connection Response Code ${response.code} (Message: ${response.message})") return false } } } } catch (sslhe: SSLHandshakeException) { - MTLog.w(this, sslhe, "SSL error!") + MTLog.w(LOG_TAG, sslhe, "SSL error!") SecurityUtils.logCertPathValidatorException(sslhe) GtfsRealTimeStorage.saveTripUpdateLastUpdateCode(context, 567) // SSL certificate not trusted (on this device) GtfsRealTimeStorage.saveTripUpdateLastUpdateMs(context, TimeUtils.currentTimeMillis()) return false } catch (uhe: UnknownHostException) { if (MTLog.isLoggable(Log.DEBUG)) { - MTLog.w(this@GTFSRealTimeTripUpdatesProvider, uhe, "No Internet Connection!") + MTLog.w(LOG_TAG, uhe, "No Internet Connection!") } else { - MTLog.w(this@GTFSRealTimeTripUpdatesProvider, "No Internet Connection!") + MTLog.w(LOG_TAG, "No Internet Connection!") } return false } catch (se: SocketException) { - MTLog.w(this@GTFSRealTimeTripUpdatesProvider, se, "No Internet Connection!") + MTLog.w(LOG_TAG, se, "No Internet Connection!") return false } catch (e: Exception) { // Unknown error - MTLog.e(this@GTFSRealTimeTripUpdatesProvider, e, "INTERNAL ERROR: Unknown Exception") + MTLog.e(LOG_TAG, e, "INTERNAL ERROR: Unknown Exception") return false } } From 2cf8b6717b404907478aa894f8bbdc0355cc214f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mathieu=20M=C3=A9a?= Date: Thu, 2 Apr 2026 14:47:26 -0400 Subject: [PATCH 11/17] cleanup --- .../status/GTFSRealTimeTripUpdatesProvider.kt | 36 ++++++++++--------- 1 file changed, 19 insertions(+), 17 deletions(-) diff --git a/src/main/java/org/mtransit/android/commons/provider/status/GTFSRealTimeTripUpdatesProvider.kt b/src/main/java/org/mtransit/android/commons/provider/status/GTFSRealTimeTripUpdatesProvider.kt index 96448bf2..6f94b98a 100644 --- a/src/main/java/org/mtransit/android/commons/provider/status/GTFSRealTimeTripUpdatesProvider.kt +++ b/src/main/java/org/mtransit/android/commons/provider/status/GTFSRealTimeTripUpdatesProvider.kt @@ -9,7 +9,6 @@ import org.mtransit.android.commons.TimeUtils import org.mtransit.android.commons.TimeUtilsK import org.mtransit.android.commons.data.Direction import org.mtransit.android.commons.data.POIStatus -import org.mtransit.android.commons.data.RouteDirection import org.mtransit.android.commons.data.RouteDirectionStop import org.mtransit.android.commons.data.Schedule import org.mtransit.android.commons.data.arrival @@ -139,10 +138,10 @@ object GTFSRealTimeTripUpdatesProvider : MTLog.Loggable { GTFSRealTimeProvider.getAgencyTripUpdatesUrlString(context, "T") ) try { - val routeDirection = filter.routeDirectionStop.let { RouteDirection(it.route, it.direction) } - val targetAuthority = routeDirection.authority - val route = routeDirection.route - val direction = routeDirection.direction + val (targetRoute, targetDirection) = filter.routeDirectionStop.let { it.route to it.direction } + val targetAuthority = filter.targetAuthority + val targetRouteIdHash = targetRoute.originalIdHash.toString() + val targetDirectionOriginalId = targetDirection.originalDirectionIdOrNull val gFeedMessage = GFeedMessage.parseFrom(gtfsRealTimeTripUpdateFile.inputStream()) val gTripUpdates = gFeedMessage.entityList.toTripUpdates() val rdTripUpdates = gTripUpdates @@ -153,30 +152,34 @@ object GTFSRealTimeTripUpdatesProvider : MTLog.Loggable { if (tripId !in tripIds) return@filter false } parseRouteId(td)?.let { routeIdHash -> - if (routeIdHash != route.originalIdHash.toString()) return@filter false + if (routeIdHash != targetRouteIdHash) return@filter false } td.optDirectionId?.takeIf { !ignoreDirection }?.let { directionId -> - if (directionId != direction.originalDirectionIdOrNull) return@filter false + if (directionId != targetDirectionOriginalId) return@filter false } return@filter true }.takeIf { it.isNotEmpty() } rdTripUpdates ?: return makeCachedStatusFromAgencyDataFallback( - targetRouteDirection = routeDirection, - filter = filter, + targetRouteIdHash = targetRouteIdHash, + targetDirectionOriginalId = targetDirectionOriginalId, + targetUUID = filter.targetUUID, tripIds = tripIds, readFromSourceMs = readFromSourceMs, sourceLabel = sourceLabel, gTripUpdates = gTripUpdates, includeCancelledTimestamps = filter.isIncludeCancelledTimestampsOrDefault, - getRDS = { context.getRDS(targetAuthority, route.id, direction.id) } + getRDS = { context.getRDS(targetAuthority, targetRoute.id, targetDirection.id) } ) if (Constants.DEBUG) { - MTLog.d(LOG_TAG, "makeCachedStatusFromAgencyData() > GTFS {R:'${route.shortestName}'|D:${direction.headsignValue}} [${gTripUpdates.size}]: ") + MTLog.d( + LOG_TAG, + "makeCachedStatusFromAgencyData() > GTFS {R:'${targetRoute.shortestName}'|D:${targetDirection.headsignValue}} [${gTripUpdates.size}]: " + ) rdTripUpdates.forEach { (_, gTripUpdate) -> MTLog.d(LOG_TAG, "makeCachedStatusFromAgencyData() > - GTFS ${gTripUpdate.toStringExt()}.") } } - val sortedRDS = context.getRDS(targetAuthority, route.id, direction.id) + val sortedRDS = context.getRDS(targetAuthority, targetRoute.id, targetDirection.id) sortedRDS ?: return null val uuidSchedule = context.getRDSSchedule(targetAuthority, sortedRDS, filter.isIncludeCancelledTimestampsOrDefault) .takeIf { it.isNotEmpty() } @@ -243,10 +246,9 @@ object GTFSRealTimeTripUpdatesProvider : MTLog.Loggable { // fallback to generate a whole new schedule from Trip Updates (only if all STU contains time instead of delay) private fun GTFSRealTimeProvider.makeCachedStatusFromAgencyDataFallback( - targetRouteDirection: RouteDirection, - targetRouteIdHash: String = targetRouteDirection.route.originalIdHash.toString(), - targetDirectionOriginalId: Int? = targetRouteDirection.direction.originalDirectionIdOrNull, - filter: Schedule.ScheduleStatusFilter, + targetRouteIdHash: String, + targetDirectionOriginalId: Int?, + targetUUID: String, tripIds: List, readFromSourceMs: Long, sourceLabel: String, @@ -317,7 +319,7 @@ object GTFSRealTimeTripUpdatesProvider : MTLog.Loggable { ) } cacheRealTimeSchedules(uuidSchedule.values, sourceLabel, readFromSourceMs, readFromSourceMs) - return getCachedStatusS(filter.targetUUID, tripIds) + return getCachedStatusS(targetUUID, tripIds) } fun GTUStopTimeUpdate.toTimestamp( From 80323694010e2201599f9109606602a98db7e781 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mathieu=20M=C3=A9a?= Date: Thu, 2 Apr 2026 14:51:37 -0400 Subject: [PATCH 12/17] clean --- .../provider/status/GTFSRealTimeTripUpdatesProvider.kt | 8 ++------ 1 file changed, 2 insertions(+), 6 deletions(-) diff --git a/src/main/java/org/mtransit/android/commons/provider/status/GTFSRealTimeTripUpdatesProvider.kt b/src/main/java/org/mtransit/android/commons/provider/status/GTFSRealTimeTripUpdatesProvider.kt index 6f94b98a..0ba48322 100644 --- a/src/main/java/org/mtransit/android/commons/provider/status/GTFSRealTimeTripUpdatesProvider.kt +++ b/src/main/java/org/mtransit/android/commons/provider/status/GTFSRealTimeTripUpdatesProvider.kt @@ -260,14 +260,10 @@ object GTFSRealTimeTripUpdatesProvider : MTLog.Loggable { .filter { gTripUpdate -> val td = gTripUpdate.optTrip ?: return@filter false parseRouteId(td)?.let { routeIdHash -> - if (routeIdHash != targetRouteIdHash) { - return@filter false - } + if (routeIdHash != targetRouteIdHash) return@filter false } td.optDirectionId?.takeIf { !ignoreDirection }?.let { directionId -> - if (directionId != targetDirectionOriginalId) { - return@filter false - } + if (directionId != targetDirectionOriginalId) return@filter false } if (td.optScheduleRelationship == GTDScheduleRelationship.DELETED) return@filter false if (!includeCancelledTimestamps && td.optScheduleRelationship == GTDScheduleRelationship.CANCELED) return@filter false From 04a33aa903989288a3891937d38bb2265f23b69c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mathieu=20M=C3=A9a?= Date: Thu, 2 Apr 2026 14:55:55 -0400 Subject: [PATCH 13/17] fix --- .../commons/provider/status/GTFSRealTimeTripUpdatesProvider.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/java/org/mtransit/android/commons/provider/status/GTFSRealTimeTripUpdatesProvider.kt b/src/main/java/org/mtransit/android/commons/provider/status/GTFSRealTimeTripUpdatesProvider.kt index 0ba48322..bb251a21 100644 --- a/src/main/java/org/mtransit/android/commons/provider/status/GTFSRealTimeTripUpdatesProvider.kt +++ b/src/main/java/org/mtransit/android/commons/provider/status/GTFSRealTimeTripUpdatesProvider.kt @@ -311,7 +311,7 @@ object GTFSRealTimeTripUpdatesProvider : MTLog.Loggable { readFromSourceAtInMs = readFromSourceMs, providerPrecisionInMs = PROVIDER_PRECISION_IN_MS, sourceLabel = sourceLabel, - noData = false, + noData = true, ) } cacheRealTimeSchedules(uuidSchedule.values, sourceLabel, readFromSourceMs, readFromSourceMs) From c4bb43be00a39a851975dfbbbfd65b0f8ba24238 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mathieu=20M=C3=A9a?= Date: Thu, 2 Apr 2026 14:57:52 -0400 Subject: [PATCH 14/17] wip --- .../provider/status/GTFSRealTimeTripUpdatesProvider.kt | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/src/main/java/org/mtransit/android/commons/provider/status/GTFSRealTimeTripUpdatesProvider.kt b/src/main/java/org/mtransit/android/commons/provider/status/GTFSRealTimeTripUpdatesProvider.kt index bb251a21..f0085ed7 100644 --- a/src/main/java/org/mtransit/android/commons/provider/status/GTFSRealTimeTripUpdatesProvider.kt +++ b/src/main/java/org/mtransit/android/commons/provider/status/GTFSRealTimeTripUpdatesProvider.kt @@ -202,6 +202,7 @@ object GTFSRealTimeTripUpdatesProvider : MTLog.Loggable { sourceLabel: String, lastUpdateInMs: Long, readFromSourceMs: Long, + ignorePastRealTime: Boolean = false, tripsWithRealTime: Set = scheduleList .asSequence() .mapNotNull { schedule -> schedule.timestamps.takeIf { it.isNotEmpty() } }.flatten() @@ -223,8 +224,10 @@ object GTFSRealTimeTripUpdatesProvider : MTLog.Loggable { var oldestDateForRealTime = now - OLDEST_FOR_REAL_TIME var maxFutureDateForRealTime = now + MAX_FUTURE_FOR_REAL_TIME val (past, future) = schedule.timestamps.partition { it.departure < now } - oldestDateForRealTime = past.filter { it.isRealTime }.minOfOrNull { it.arrival } // all real-time - ?: oldestDateForRealTime + if (!ignorePastRealTime) { + oldestDateForRealTime = past.filter { it.isRealTime }.minOfOrNull { it.arrival } // all real-time + ?: oldestDateForRealTime + } maxFutureDateForRealTime = future.take(10).maxOfOrNull { it.departure } // keep firsts 10 ?.takeIf { it > maxFutureDateForRealTime } ?: maxFutureDateForRealTime From 0c417e96d2bfd399e26cd4be4886b9a58dbf00e1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mathieu=20M=C3=A9a?= Date: Thu, 2 Apr 2026 15:08:12 -0400 Subject: [PATCH 15/17] fix --- .../provider/status/GTFSRealTimeTripUpdatesProvider.kt | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/main/java/org/mtransit/android/commons/provider/status/GTFSRealTimeTripUpdatesProvider.kt b/src/main/java/org/mtransit/android/commons/provider/status/GTFSRealTimeTripUpdatesProvider.kt index f0085ed7..7bc0612f 100644 --- a/src/main/java/org/mtransit/android/commons/provider/status/GTFSRealTimeTripUpdatesProvider.kt +++ b/src/main/java/org/mtransit/android/commons/provider/status/GTFSRealTimeTripUpdatesProvider.kt @@ -237,8 +237,8 @@ object GTFSRealTimeTripUpdatesProvider : MTLog.Loggable { // remove timestamps that are not real-time & outside of min/max date for real-time schedule.timestamps .filter { timestamp -> - !timestamp.isRealTime - && (timestamp.arrival <= oldestDateForRealTime || maxFutureDateForRealTime <= timestamp.departure) + (timestamp.arrival <= oldestDateForRealTime && (!timestamp.isRealTime || ignorePastRealTime)) + || (maxFutureDateForRealTime <= timestamp.departure && !timestamp.isRealTime) } .forEach { timestamp -> schedule.removeTimestamp(timestamp) @@ -317,7 +317,7 @@ object GTFSRealTimeTripUpdatesProvider : MTLog.Loggable { noData = true, ) } - cacheRealTimeSchedules(uuidSchedule.values, sourceLabel, readFromSourceMs, readFromSourceMs) + cacheRealTimeSchedules(uuidSchedule.values, sourceLabel, readFromSourceMs, readFromSourceMs, ignorePastRealTime = true) return getCachedStatusS(targetUUID, tripIds) } From 27176c42bb85fe37a1579636a0c463e3d903bf52 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mathieu=20M=C3=A9a?= Date: Tue, 7 Apr 2026 16:13:31 -0400 Subject: [PATCH 16/17] wip --- .../java/org/mtransit/android/MtLogExt.kt | 2 +- .../android/commons/data/ScheduleExt.kt | 7 ++++-- .../provider/GTFSRealTimeProvider.java | 2 +- .../commons/provider/gtfs/GtfsRealtimeExt.kt | 2 ++ .../status/GTFSRealTimeTripUpdatesProvider.kt | 22 ++++++++++++++----- .../GTFSRealTimeVehiclePositionsProvider.kt | 2 +- 6 files changed, 26 insertions(+), 11 deletions(-) diff --git a/src/main/java/org/mtransit/android/MtLogExt.kt b/src/main/java/org/mtransit/android/MtLogExt.kt index 4230a68b..f0727c40 100644 --- a/src/main/java/org/mtransit/android/MtLogExt.kt +++ b/src/main/java/org/mtransit/android/MtLogExt.kt @@ -12,7 +12,7 @@ import kotlin.time.Instant fun Long?.toDurationLog() = MTLog.formatDuration(this) fun Date?.toDurationLog() = this?.time.toDurationLog() fun Calendar?.toDurationLog() = this?.timeInMillis.toDurationLog() -fun Duration?.toDurationLog() = this?.inWholeMilliseconds.toDateTimeLog() +fun Duration?.toDurationLog() = this?.inWholeMilliseconds.toDurationLog() fun Long?.toDateTimeLog() = MTLog.formatDateTime(this) fun Date?.toDateTimeLog() = this?.time.toDateTimeLog() diff --git a/src/main/java/org/mtransit/android/commons/data/ScheduleExt.kt b/src/main/java/org/mtransit/android/commons/data/ScheduleExt.kt index 319b4d64..16c046dc 100644 --- a/src/main/java/org/mtransit/android/commons/data/ScheduleExt.kt +++ b/src/main/java/org/mtransit/android/commons/data/ScheduleExt.kt @@ -96,6 +96,9 @@ var Schedule.Timestamp.originalDepartureDelay: Duration val Schedule.Timestamp.originalDeparture get() = departure - originalDepartureDelay +val Schedule.Timestamp.maxDate get() = maxOf(originalDeparture, departure, originalArrival, arrival) +val Schedule.Timestamp.minDate get() = minOf(originalDeparture, departure, originalArrival, arrival) + /** * It's better to be early at the stop, than late and miss the vehicle departure -> truncate (floor by) to early w/ precision */ @@ -205,13 +208,13 @@ fun Schedule.Timestamp.toStringShort() = buildString { arrivalTIfDifferent?.let { append("a=").append(if (Constants.DEBUG) arrivalT.toDateTimeLog() else arrivalT) if (originalArrivalDelayMs != 0L) { - append("[+/-:").append(if (Constants.DEBUG) originalArrivalDelayMs.toDateTimeLog() else originalArrivalDelayMs).append("]") + append("[+/-:").append(if (Constants.DEBUG) originalArrivalDelayMs.toDurationLog() else originalArrivalDelayMs).append("]") } append(",") } append("d=").append(if (Constants.DEBUG) departureT.toDateTimeLog() else departureT) if (originalDepartureDelayMs != 0L) { - append("[+/-:").append(if (Constants.DEBUG) originalDepartureDelayMs.toDateTimeLog() else originalDepartureDelayMs).append("]") + append("[+/-:").append(if (Constants.DEBUG) originalDepartureDelayMs.toDurationLog() else originalDepartureDelayMs).append("]") } if (tripId != null) { append("[tId:").append(tripId).append("]") diff --git a/src/main/java/org/mtransit/android/commons/provider/GTFSRealTimeProvider.java b/src/main/java/org/mtransit/android/commons/provider/GTFSRealTimeProvider.java index f581f774..d9cb524e 100644 --- a/src/main/java/org/mtransit/android/commons/provider/GTFSRealTimeProvider.java +++ b/src/main/java/org/mtransit/android/commons/provider/GTFSRealTimeProvider.java @@ -901,7 +901,7 @@ private List loadAgencyServiceUpdateDataFromWWW(@NonNull Context final GtfsRealtime.Alert gAlert = gAlertAndId.getFirst(); final String feedEntityId = gAlertAndId.getSecond(); if (Constants.DEBUG) { - MTLog.d(this, "loadAgencyServiceUpdateDataFromWWW() > - GTFS[%s] %s", feedEntityId, GtfsRealtimeExt.toStringExt(gAlert)); + MTLog.d(this, "loadAgencyServiceUpdateDataFromWWW() > GTFS - [%s] %s", feedEntityId, GtfsRealtimeExt.toStringExt(gAlert)); } final Set alertsServiceUpdates = processAlerts(context, sourceLabel, feedEntityId, newLastUpdateInMs, gAlert, ignoreDirection); if (alertsServiceUpdates != null && !alertsServiceUpdates.isEmpty()) { diff --git a/src/main/java/org/mtransit/android/commons/provider/gtfs/GtfsRealtimeExt.kt b/src/main/java/org/mtransit/android/commons/provider/gtfs/GtfsRealtimeExt.kt index 6dc8e646..2c711566 100644 --- a/src/main/java/org/mtransit/android/commons/provider/gtfs/GtfsRealtimeExt.kt +++ b/src/main/java/org/mtransit/android/commons/provider/gtfs/GtfsRealtimeExt.kt @@ -228,6 +228,7 @@ object GtfsRealtimeExt { val GTripUpdate.optTimestamp get() = if (hasTimestamp()) timestamp else null val GTripUpdate.optTimestampMs get() = optTimestamp?.secToMs() val GTripUpdate.optDelay get() = if (hasDelay()) delay else null + val GTripUpdate.optDelayMs get() = this.optDelay?.secToMs() val GTripUpdate.optDelayDuration get() = this.optDelay?.seconds val GTripUpdate.optTripProperties get() = if (hasTripProperties()) tripProperties else null @@ -284,6 +285,7 @@ object GtfsRealtimeExt { } val GTUStopTimeEvent.optDelay get() = if (hasDelay()) delay else null + val GTUStopTimeEvent.optDelayMs get() = this.optDelay?.secToMs() val GTUStopTimeEvent.optDelayDuration: Duration? get() = this.optDelay?.seconds val GTUStopTimeEvent.optTime get() = if (hasTime()) time else null val GTUStopTimeEvent.optTimeMs get() = optTime?.secToMs() diff --git a/src/main/java/org/mtransit/android/commons/provider/status/GTFSRealTimeTripUpdatesProvider.kt b/src/main/java/org/mtransit/android/commons/provider/status/GTFSRealTimeTripUpdatesProvider.kt index 7bc0612f..e19ebd31 100644 --- a/src/main/java/org/mtransit/android/commons/provider/status/GTFSRealTimeTripUpdatesProvider.kt +++ b/src/main/java/org/mtransit/android/commons/provider/status/GTFSRealTimeTripUpdatesProvider.kt @@ -14,10 +14,13 @@ import org.mtransit.android.commons.data.Schedule import org.mtransit.android.commons.data.arrival import org.mtransit.android.commons.data.departure import org.mtransit.android.commons.data.makeSchedule +import org.mtransit.android.commons.data.maxDate +import org.mtransit.android.commons.data.minDate import org.mtransit.android.commons.data.toNoData import org.mtransit.android.commons.provider.GTFSRealTimeProvider import org.mtransit.android.commons.provider.gtfs.GtfsRealTimeStorage import org.mtransit.android.commons.provider.gtfs.GtfsRealtimeExt.optArrival +import org.mtransit.android.commons.provider.gtfs.GtfsRealtimeExt.optDelayMs import org.mtransit.android.commons.provider.gtfs.GtfsRealtimeExt.optDeparture import org.mtransit.android.commons.provider.gtfs.GtfsRealtimeExt.optDirectionId import org.mtransit.android.commons.provider.gtfs.GtfsRealtimeExt.optPickupType @@ -176,7 +179,7 @@ object GTFSRealTimeTripUpdatesProvider : MTLog.Loggable { "makeCachedStatusFromAgencyData() > GTFS {R:'${targetRoute.shortestName}'|D:${targetDirection.headsignValue}} [${gTripUpdates.size}]: " ) rdTripUpdates.forEach { (_, gTripUpdate) -> - MTLog.d(LOG_TAG, "makeCachedStatusFromAgencyData() > - GTFS ${gTripUpdate.toStringExt()}.") + MTLog.d(LOG_TAG, "makeCachedStatusFromAgencyData() > GTFS - ${gTripUpdate.toStringExt()}.") } } val sortedRDS = context.getRDS(targetAuthority, targetRoute.id, targetDirection.id) @@ -189,7 +192,7 @@ object GTFSRealTimeTripUpdatesProvider : MTLog.Loggable { cacheRealTimeSchedules(uuidSchedule.values, sourceLabel, readFromSourceMs, readFromSourceMs) return getCachedStatusS(filter.targetUUID, tripIds) } catch (e: Exception) { - MTLog.w(this, e, "makeCachedStatusFromAgencyData() > error!") + MTLog.w(LOG_TAG, e, "makeCachedStatusFromAgencyData() > error!") return null } } @@ -237,8 +240,8 @@ object GTFSRealTimeTripUpdatesProvider : MTLog.Loggable { // remove timestamps that are not real-time & outside of min/max date for real-time schedule.timestamps .filter { timestamp -> - (timestamp.arrival <= oldestDateForRealTime && (!timestamp.isRealTime || ignorePastRealTime)) - || (maxFutureDateForRealTime <= timestamp.departure && !timestamp.isRealTime) + ((!timestamp.isRealTime || ignorePastRealTime) && timestamp.maxDate <= oldestDateForRealTime) + || (!timestamp.isRealTime && maxFutureDateForRealTime <= timestamp.minDate) } .forEach { timestamp -> schedule.removeTimestamp(timestamp) @@ -271,7 +274,8 @@ object GTFSRealTimeTripUpdatesProvider : MTLog.Loggable { if (td.optScheduleRelationship == GTDScheduleRelationship.DELETED) return@filter false if (!includeCancelledTimestamps && td.optScheduleRelationship == GTDScheduleRelationship.CANCELED) return@filter false return@filter true - }.takeIf { it.isNotEmpty() } + } + .takeIf { it.isNotEmpty() } ?: return null val notAllWithTime = rdTripUpdates.flatMap { it.optStopTimeUpdateList.orEmpty() }.any { it.optDeparture?.optTime == null && it.optArrival?.optTime == null } @@ -345,6 +349,12 @@ object GTFSRealTimeTripUpdatesProvider : MTLog.Loggable { if (Constants.DEBUG) { tripId = gTripUpdate.optTrip?.tripId // this trip ID does NOT match static data! } + optDeparture?.optDelayMs?.let { + this.originalDepartureDelayMs = it + } + optArrival?.optDelayMs?.let { + this.originalArrivalDelayMs = it + } realTime = true arrival?.optTimeMs?.let { this.arrivalT = it } if (scheduleRelationship == GTUSTUScheduleRelationship.SKIPPED) { @@ -449,7 +459,7 @@ object GTFSRealTimeTripUpdatesProvider : MTLog.Loggable { val gTripUpdates = gFeedMessage.entityList.toTripUpdates() MTLog.d(LOG_TAG, "loadAgencyDataFromWWW() > GTFS trip updates[${gTripUpdates.size}]: ") gTripUpdates.sortTripUpdates(TimeUtils.currentTimeMillis()).forEach { gTripUpdate -> - MTLog.d(LOG_TAG, "loadAgencyDataFromWWW() > - GTFS ${gTripUpdate.toStringExt()}") + MTLog.d(LOG_TAG, "loadAgencyDataFromWWW() > GTFS - ${gTripUpdate.toStringExt()}") } } } catch (e: IOException) { diff --git a/src/main/java/org/mtransit/android/commons/provider/vehiclelocations/GTFSRealTimeVehiclePositionsProvider.kt b/src/main/java/org/mtransit/android/commons/provider/vehiclelocations/GTFSRealTimeVehiclePositionsProvider.kt index a7d72afd..5d054070 100644 --- a/src/main/java/org/mtransit/android/commons/provider/vehiclelocations/GTFSRealTimeVehiclePositionsProvider.kt +++ b/src/main/java/org/mtransit/android/commons/provider/vehiclelocations/GTFSRealTimeVehiclePositionsProvider.kt @@ -188,7 +188,7 @@ object GTFSRealTimeVehiclePositionsProvider { } for (gVehiclePosition in gVehiclePositions.sortVehicles(newLastUpdateInMs)) { if (Constants.DEBUG) { - MTLog.d(this@GTFSRealTimeVehiclePositionsProvider, "loadAgencyDataFromWWW() > - GTFS ${gVehiclePosition.toStringExt()}.") + MTLog.d(this@GTFSRealTimeVehiclePositionsProvider, "loadAgencyDataFromWWW() > GTFS - ${gVehiclePosition.toStringExt()}.") } processVehiclePositions(newLastUpdateInMs, gVehiclePosition, ignoreDirection) ?.takeIf { it.isNotEmpty() } From c4da88df730361bb8772d6247a2f01001b49cb4e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mathieu=20M=C3=A9a?= Date: Tue, 7 Apr 2026 16:22:53 -0400 Subject: [PATCH 17/17] clean --- src/main/java/org/mtransit/android/commons/data/ScheduleExt.kt | 2 +- .../commons/provider/status/GTFSRealTimeTripUpdatesProvider.kt | 2 -- 2 files changed, 1 insertion(+), 3 deletions(-) diff --git a/src/main/java/org/mtransit/android/commons/data/ScheduleExt.kt b/src/main/java/org/mtransit/android/commons/data/ScheduleExt.kt index aa3a6f52..16c046dc 100644 --- a/src/main/java/org/mtransit/android/commons/data/ScheduleExt.kt +++ b/src/main/java/org/mtransit/android/commons/data/ScheduleExt.kt @@ -226,4 +226,4 @@ fun Schedule.Timestamp.toStringShort() = buildString { append("[OLD]") } append("}") -} \ No newline at end of file +} diff --git a/src/main/java/org/mtransit/android/commons/provider/status/GTFSRealTimeTripUpdatesProvider.kt b/src/main/java/org/mtransit/android/commons/provider/status/GTFSRealTimeTripUpdatesProvider.kt index 24c56891..10bddbf2 100644 --- a/src/main/java/org/mtransit/android/commons/provider/status/GTFSRealTimeTripUpdatesProvider.kt +++ b/src/main/java/org/mtransit/android/commons/provider/status/GTFSRealTimeTripUpdatesProvider.kt @@ -145,8 +145,6 @@ object GTFSRealTimeTripUpdatesProvider : MTLog.Loggable { val targetAuthority = filter.targetAuthority val targetRouteIdHash = targetRoute.originalIdHash.toString() val targetDirectionOriginalId = targetDirection.originalDirectionIdOrNull - val gFeedMessage = GFeedMessage.parseFrom(gtfsRealTimeTripUpdateFile.inputStream()) - val gTripUpdates = gFeedMessage.entityList.toTripUpdates() val rdTripUpdates = gTripUpdates .mapNotNull { gTripUpdate -> gTripUpdate.optTrip?.let { it to gTripUpdate }