Skip to content

Commit ade3519

Browse files
Merge pull request #1554 from bmander/animated-markers
Animated vehicle markers with speed-based extrapolation
2 parents 9cfaf20 + 0bb532f commit ade3519

91 files changed

Lines changed: 10801 additions & 1372 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

.gitignore

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,4 +7,8 @@
77
settings.local.json
88
.kotlin/
99
play-store-upload-key.json
10-
*.keystore
10+
*.keystore
11+
.vscode/
12+
.project
13+
.classpath
14+
bin/

onebusaway-android/build.gradle

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -67,13 +67,15 @@ android {
6767
isDefault = true
6868
buildConfigField "String", "MAP_FRAGMENT_CLASS", "\"org.onebusaway.android.map.googlemapsv2.BaseMapFragment\""
6969
buildConfigField "String", "MAP_HELPER_CLASS", "\"org.onebusaway.android.map.googlemapsv2.GoogleProprietaryMapHelper\""
70+
buildConfigField "String", "TRIP_MAP_FRAGMENT_CLASS", "\"org.onebusaway.android.map.googlemapsv2.tripmap.TripMapFragment\""
7071
}
7172

7273
maplibre {
7374
// MapLibre-based build using OpenFreeMap tiles - see src/maplibre
7475
dimension "platform"
7576
buildConfigField "String", "MAP_FRAGMENT_CLASS", "\"org.onebusaway.android.map.maplibre.MapLibreMapFragment\""
7677
buildConfigField "String", "MAP_HELPER_CLASS", "\"org.onebusaway.android.map.maplibre.MapLibreProprietaryMapHelper\""
78+
buildConfigField "String", "TRIP_MAP_FRAGMENT_CLASS", "\"org.onebusaway.android.map.maplibre.tripmap.TripMapFragment\""
7779
}
7880

7981
// Brand flavors are loaded from separate files in flavors/ directory.
@@ -265,7 +267,9 @@ dependencies {
265267
implementation 'com.google.android.material:material:1.12.0'
266268
// Explicit dependency on Gson is now apparently needed
267269
implementation 'com.google.code.gson:gson:2.10.1'
268-
// Unit tests - seems like this is still necessary w/ Android X even though useLibrary is declared earlier
270+
// JVM unit tests
271+
testImplementation 'junit:junit:4.13.2'
272+
// Instrumented tests - seems like this is still necessary w/ Android X even though useLibrary is declared earlier
269273
androidTestImplementation 'androidx.test:runner:1.6.2'
270274
// WorkManager (Java only)
271275
implementation 'androidx.work:work-runtime:2.9.1'
@@ -279,6 +283,9 @@ dependencies {
279283
implementation "androidx.room:room-runtime:2.7.0"
280284
ksp "androidx.room:room-compiler:2.7.0"
281285
implementation "androidx.room:room-ktx:2.7.0"
286+
// Coroutines
287+
implementation "org.jetbrains.kotlinx:kotlinx-coroutines-core:1.8.1"
288+
implementation "org.jetbrains.kotlinx:kotlinx-coroutines-android:1.8.1"
282289
// GTFS Realtime bindings for parsing GTFS-realtime data
283290
implementation "org.mobilitydata:gtfs-realtime-bindings:0.0.8"
284291
implementation 'com.google.api.grpc:proto-google-common-protos:2.9.0'
Lines changed: 132 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,132 @@
1+
/*
2+
* Copyright (C) 2024-2026 Open Transit Software Foundation
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
package org.onebusaway.android.extrapolation.test
17+
18+
import androidx.test.InstrumentationRegistry.getTargetContext
19+
import org.junit.Assert.assertEquals
20+
import org.junit.Assert.assertNotNull
21+
import org.junit.Assert.assertNull
22+
import org.junit.Assert.assertSame
23+
import org.junit.Assert.assertTrue
24+
import org.junit.Test
25+
import org.onebusaway.android.app.Application
26+
import org.onebusaway.android.extrapolation.data.toObservations
27+
import org.onebusaway.android.io.request.ObaTripDetailsRequest
28+
import org.onebusaway.android.io.request.ObaTripsForRouteRequest
29+
import org.onebusaway.android.io.test.ObaTestCase
30+
import org.onebusaway.android.mock.MockRegion
31+
32+
/**
33+
* Tests the response adapters (Adapters.kt) against mock API responses from /res/raw: the
34+
* distillation of trip-details and trips-for-route responses into TripObservations, including
35+
* the skip guards for responses without a status or an active trip ID.
36+
*/
37+
class AdaptersTest : ObaTestCase() {
38+
39+
companion object {
40+
private const val HART_TRIP_ID = "Hillsborough Area Regional Transit_1389962"
41+
private const val HART_ROUTE_ID = "Hillsborough Area Regional Transit_5"
42+
}
43+
44+
// --- ObaTripDetailsResponse.toObservations ---
45+
46+
@Test
47+
fun detailsResponseDistillsOneObservation() {
48+
Application.get().setCurrentRegion(MockRegion.getTampa(getTargetContext()))
49+
val response =
50+
ObaTripDetailsRequest.Builder(getTargetContext(), HART_TRIP_ID).build().call()
51+
assertOK(response)
52+
53+
val observations = response.toObservations()
54+
assertEquals(1, observations.size)
55+
56+
val observation = observations[0]
57+
assertEquals(HART_TRIP_ID, observation.tripId)
58+
assertSame(response.status, observation.status)
59+
assertEquals(response.currentTime, observation.serverTimeMs)
60+
assertEquals(1545886800000L, observation.serviceDate)
61+
// Resolved through the refs: trip 1389962 -> route 16 -> type 3 (bus)
62+
assertEquals(3, observation.routeType ?: -1)
63+
}
64+
65+
@Test
66+
fun detailsResponseWithoutStatusYieldsNoObservations() {
67+
// Puget Sound fixture: schedule and refs present, no vehicle status
68+
val response =
69+
ObaTripDetailsRequest.Builder(getTargetContext(), "1_18196913").build().call()
70+
assertOK(response)
71+
assertNull(response.status)
72+
73+
assertTrue(response.toObservations().isEmpty())
74+
}
75+
76+
@Test
77+
fun detailsResponseWithoutActiveTripIdYieldsNoObservations() {
78+
Application.get().setCurrentRegion(MockRegion.getTampa(getTargetContext()))
79+
val response =
80+
ObaTripDetailsRequest.Builder(
81+
getTargetContext(),
82+
"${HART_TRIP_ID}_no_active_trip"
83+
)
84+
.build()
85+
.call()
86+
assertOK(response)
87+
// Status present but the vehicle reports no active trip (e.g. between runs)
88+
assertNotNull(response.status)
89+
assertNull(response.status!!.activeTripId)
90+
91+
assertTrue(response.toObservations().isEmpty())
92+
}
93+
94+
// --- ObaTripsForRouteResponse.toObservations ---
95+
96+
@Test
97+
fun tripsForRouteResponseDistillsOneObservationPerActiveTrip() {
98+
Application.get().setCurrentRegion(MockRegion.getTampa(getTargetContext()))
99+
val response =
100+
ObaTripsForRouteRequest.Builder(getTargetContext(), HART_ROUTE_ID)
101+
.setIncludeStatus(true)
102+
.build()
103+
.call()
104+
assertOK(response)
105+
106+
val observations = response.toObservations()
107+
assertEquals(38, observations.size)
108+
109+
val first = observations[0]
110+
assertEquals("Hillsborough Area Regional Transit_101446", first.tripId)
111+
assertEquals(response.currentTime, first.serverTimeMs)
112+
assertEquals(1444017600000L, first.serviceDate)
113+
assertEquals(3, first.routeType ?: -1)
114+
115+
// Each observation is keyed by the trip its vehicle reported as active
116+
for (observation in observations) {
117+
assertEquals(observation.status.activeTripId, observation.tripId)
118+
}
119+
}
120+
121+
@Test
122+
fun tripsForRouteResponseWithoutStatusesYieldsNoObservations() {
123+
Application.get().setCurrentRegion(MockRegion.getTampa(getTargetContext()))
124+
val response =
125+
ObaTripsForRouteRequest.Builder(getTargetContext(), HART_ROUTE_ID)
126+
.build()
127+
.call()
128+
assertOK(response)
129+
130+
assertTrue(response.toObservations().isEmpty())
131+
}
132+
}

0 commit comments

Comments
 (0)