From ca2c5fe38506ee48cb85c3773badb34700548cf3 Mon Sep 17 00:00:00 2001 From: PoliEcho Date: Wed, 28 Jan 2026 15:00:44 +0100 Subject: [PATCH] add GTFS static data --- .../org/pupes/mhdrunpathfinder/GTFSUtils.kt | 184 ++++++++++++++++++ .../org/pupes/mhdrunpathfinder/HaukUtils.kt | 10 +- .../org/pupes/mhdrunpathfinder/Helpers.kt | 5 + .../pupes/mhdrunpathfinder/MainActivity.kt | 8 +- .../org/pupes/mhdrunpathfinder/PIDUtils.kt | 2 - .../drawable/outline_directions_bus_24.xml | 5 + app/src/main/res/values/strings.xml | 3 + 7 files changed, 204 insertions(+), 13 deletions(-) create mode 100644 app/src/main/java/org/pupes/mhdrunpathfinder/GTFSUtils.kt delete mode 100644 app/src/main/java/org/pupes/mhdrunpathfinder/PIDUtils.kt create mode 100644 app/src/main/res/drawable/outline_directions_bus_24.xml diff --git a/app/src/main/java/org/pupes/mhdrunpathfinder/GTFSUtils.kt b/app/src/main/java/org/pupes/mhdrunpathfinder/GTFSUtils.kt new file mode 100644 index 0000000..45b290c --- /dev/null +++ b/app/src/main/java/org/pupes/mhdrunpathfinder/GTFSUtils.kt @@ -0,0 +1,184 @@ +package org.pupes.mhdrunpathfinder + +import android.app.Activity +import android.os.Handler +import android.os.Looper +import android.widget.Toast +import androidx.compose.foundation.layout.size +import androidx.compose.material3.Icon +import androidx.compose.ui.Modifier +import androidx.compose.ui.graphics.Color +import androidx.compose.ui.res.painterResource +import androidx.compose.ui.unit.dp +import kotlinx.serialization.Serializable +import okhttp3.OkHttpClient +import okhttp3.Request +import ovh.plrapps.mapcompose.api.addMarker +import java.net.URL + +@Serializable +data class GTFSRoute( + val agency_id: String, + val is_night: Boolean, + val route_color: String, + val route_desc: String? = null, + val route_id: String, + val route_long_name: String, + val route_short_name: String, + val route_text_color: String, + val route_type: Int, + val route_url: String, + val is_regional: Boolean, + val is_substitute_transport: Boolean +) + +// Type alias for array of GTFS routes +typealias GTFSRoutes = List + +var gtfsRoutes: GTFSRoutes? = null + +@Serializable +data class GTFSStopGeometry( + val coordinates: List, + val type: String +) + +@Serializable +data class GTFSStopProperties( + val location_type: Int, + val parent_station: String? = null, + val platform_code: String? = null, + val stop_id: String, + val stop_name: String, + val wheelchair_boarding: Int, + val zone_id: String? = null, + val level_id: String? = null +) + +@Serializable +data class GTFSStop( + val geometry: GTFSStopGeometry, + val properties: GTFSStopProperties, + val type: String +) + +@Serializable +data class GTFSStops( + val features: List, + val type: String +) + +var gtfsStops: GTFSStops? = null + + +// OkHttpClient for making HTTP requests +val gtfshttpClient = OkHttpClient.Builder() + .build() + +private val GTFSMainHandler = Handler(Looper.getMainLooper()) +private var GTFSTrackingThread: Thread? = null +private var GTFSIsTracking = false + +private fun fetchGTFSPosition(gtfsUrl: URL,api_key: String, callee: Activity) { + try { + + val request = Request.Builder() + .url("${gtfsUrl.protocol}://${gtfsUrl.host}${"/"}") + .header("User-Agent", "${BuildConfig.APPLICATION_ID}/${BuildConfig.VERSION_NAME} (Android)") + .build() + + gtfshttpClient.newCall(request).execute().use { response -> + var message = if (response.isSuccessful) { + response.body?.string() ?: "Empty response" + } else { + "Error: ${response.code} ${response.message}" + } + + // TODO: Handle response + } + } catch (e: Exception) { + // Post UI update to main thread + GTFSMainHandler.post { + Toast.makeText(callee, e.message ?: "Unknown error", Toast.LENGTH_SHORT).show() + } + } +} + +// Start the timer with a URL: +fun startGTFSTracking(url: String, api_key: String, callee: Activity) { + if (GTFSIsTracking) return + + GTFSIsTracking = true + GTFSTrackingThread = Thread { + var gtfsUrl = URL(url); + try { + if(gtfsRoutes == null) { + val request = Request.Builder() + .url("${gtfsUrl.protocol}://${gtfsUrl.host}${callee.getString(R.string.routes_path)}") + .header( + "User-Agent", + "${BuildConfig.APPLICATION_ID}/${BuildConfig.VERSION_NAME} (Android)" + ) + .header("X-Access-Token", api_key).build(); + + gtfshttpClient.newCall(request).execute().use { response -> + var message = if (response.isSuccessful) { + response.body?.string() ?: "Empty response" + } else { + "Error: ${response.code} ${response.message}" + } + gtfsRoutes = json.decodeFromString(message) + }} + if(gtfsStops == null) { + val request = Request.Builder() + .url("${gtfsUrl.protocol}://${gtfsUrl.host}${callee.getString(R.string.stops_path)}") + .header( + "User-Agent", + "${BuildConfig.APPLICATION_ID}/${BuildConfig.VERSION_NAME} (Android)" + ) + .header("X-Access-Token", api_key).build(); + + gtfshttpClient.newCall(request).execute().use { response -> + var message = if (response.isSuccessful) { + response.body?.string() ?: "Empty response" + } else { + "Error: ${response.code} ${response.message}" + } + gtfsStops = json.decodeFromString(message) + + // put all stops on the map + for (stop in gtfsStops!!.features) { + GTFSMainHandler.post { + mapState.addMarker(stop.properties.stop_id, x = longitudeToXNormalized(stop.geometry.coordinates[0]), y = latitudeToYNormalized(stop.geometry.coordinates[0])) { + Icon( + painter = painterResource(id = R.drawable.outline_directions_bus_24), + contentDescription = null, + modifier = Modifier.size(17.dp), + tint = Color(0xFF0000FF) + ) + } + + } + } + }} + } catch(e: Exception) { + Toast.makeText(callee, e.message ?: "Unknown error", Toast.LENGTH_SHORT).show() + GTFSMainHandler.post { stopGTFSTracking() } + return@Thread + } + while (GTFSIsTracking) { + fetchGTFSPosition(gtfsUrl,api_key, callee) + try { + Thread.sleep(1000) // Wait 1 second between requests + } catch (e: InterruptedException) { + break + } + } + }.apply { start() } +} + +fun stopGTFSTracking() { + GTFSIsTracking = false + GTFSTrackingThread?.interrupt() + GTFSTrackingThread = null +} \ No newline at end of file diff --git a/app/src/main/java/org/pupes/mhdrunpathfinder/HaukUtils.kt b/app/src/main/java/org/pupes/mhdrunpathfinder/HaukUtils.kt index 55ef33d..784965b 100644 --- a/app/src/main/java/org/pupes/mhdrunpathfinder/HaukUtils.kt +++ b/app/src/main/java/org/pupes/mhdrunpathfinder/HaukUtils.kt @@ -4,7 +4,6 @@ import android.app.Activity import android.os.Handler import android.os.Looper import android.widget.Toast -import androidx.compose.ui.semantics.getOrNull import kotlinx.serialization.Serializable import kotlinx.serialization.json.Json import okhttp3.OkHttpClient @@ -22,12 +21,9 @@ data class HaukResponse( val salt: String? = null ) -private val json = Json { - coerceInputValues = true // Coerce nulls to default values if applicable - ignoreUnknownKeys = true // Ignore fields in JSON not present in the data class -} + // OkHttpClient for making HTTP requests -val httpClient = OkHttpClient.Builder() +val haukhttpClient = OkHttpClient.Builder() .build() private val HaukMainHandler = Handler(Looper.getMainLooper()) @@ -44,7 +40,7 @@ private fun fetchHaukPosition(haukUrl: String, callee: Activity) { .header("User-Agent", "${BuildConfig.APPLICATION_ID}/${BuildConfig.VERSION_NAME} (Android)") .build() - httpClient.newCall(request).execute().use { response -> + haukhttpClient.newCall(request).execute().use { response -> var message = if (response.isSuccessful) { response.body?.string() ?: "Empty response" } else { diff --git a/app/src/main/java/org/pupes/mhdrunpathfinder/Helpers.kt b/app/src/main/java/org/pupes/mhdrunpathfinder/Helpers.kt index 8635262..1a6b02a 100644 --- a/app/src/main/java/org/pupes/mhdrunpathfinder/Helpers.kt +++ b/app/src/main/java/org/pupes/mhdrunpathfinder/Helpers.kt @@ -1,9 +1,14 @@ package org.pupes.mhdrunpathfinder +import kotlinx.serialization.json.Json import kotlin.math.PI import kotlin.math.ln import kotlin.math.tan +val json = Json { + coerceInputValues = true // Coerce nulls to default values if applicable + ignoreUnknownKeys = true // Ignore fields in JSON not present in the data class +} fun longitudeToXNormalized(longitude: Double): Double { return (longitude + 180.0) / 360.0 } diff --git a/app/src/main/java/org/pupes/mhdrunpathfinder/MainActivity.kt b/app/src/main/java/org/pupes/mhdrunpathfinder/MainActivity.kt index 9529e79..59e348a 100644 --- a/app/src/main/java/org/pupes/mhdrunpathfinder/MainActivity.kt +++ b/app/src/main/java/org/pupes/mhdrunpathfinder/MainActivity.kt @@ -99,7 +99,7 @@ fun MainScreen(callee: Activity) { if (golemioAPIkey.isNotEmpty()) { golemioState = true - /*TODO: START MHD TRACKING*/ + startGTFSTracking(callee.getString(R.string.golemio_base_url), golemioAPIkey, callee); } Box(modifier = Modifier.fillMaxSize()) { @@ -142,7 +142,7 @@ fun MainScreen(callee: Activity) { sharedPreferences.edit { putString("golemioAPIkey", golemioAPIkey) } - /*TODO: START MHD TRACKING*/ + startGTFSTracking(callee.getString(R.string.golemio_base_url), golemioAPIkey, callee); } }, golemioState = golemioState, @@ -153,10 +153,10 @@ fun MainScreen(callee: Activity) { sharedPreferences.edit { putString("golemioAPIkey", golemioAPIkey) } - /*TODO: START MHD TRACKING*/ + startGTFSTracking(callee.getString(R.string.golemio_base_url), golemioAPIkey, callee); } } else { - /*TODO: STOP MHD TRACKING*/ + stopGTFSTracking(); } } ) diff --git a/app/src/main/java/org/pupes/mhdrunpathfinder/PIDUtils.kt b/app/src/main/java/org/pupes/mhdrunpathfinder/PIDUtils.kt deleted file mode 100644 index 15a2936..0000000 --- a/app/src/main/java/org/pupes/mhdrunpathfinder/PIDUtils.kt +++ /dev/null @@ -1,2 +0,0 @@ -package org.pupes.mhdrunpathfinder - diff --git a/app/src/main/res/drawable/outline_directions_bus_24.xml b/app/src/main/res/drawable/outline_directions_bus_24.xml new file mode 100644 index 0000000..cbca86c --- /dev/null +++ b/app/src/main/res/drawable/outline_directions_bus_24.xml @@ -0,0 +1,5 @@ + + + + + diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index a28da68..4ee20a8 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -1,3 +1,6 @@ MHD run Pathfinder + /v2/gtfs/routes + /v2/gtfs/stops + https://api.golemio.cz \ No newline at end of file