add GTFS static data

This commit is contained in:
PoliEcho 2026-01-28 15:00:44 +01:00
parent 077c26eec7
commit ca2c5fe385
7 changed files with 204 additions and 13 deletions

View File

@ -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<GTFSRoute>
var gtfsRoutes: GTFSRoutes? = null
@Serializable
data class GTFSStopGeometry(
val coordinates: List<Double>,
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<GTFSStop>,
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<GTFSRoutes>(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<GTFSStops>(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
}

View File

@ -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 {

View File

@ -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
}

View File

@ -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();
}
}
)

View File

@ -1,2 +0,0 @@
package org.pupes.mhdrunpathfinder

View File

@ -0,0 +1,5 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android" android:height="24dp" android:tint="#000000" android:viewportHeight="960" android:viewportWidth="960" android:width="24dp">
<path android:fillColor="@android:color/white" android:pathData="M240,840Q223,840 211.5,828.5Q200,817 200,800L200,718Q182,698 171,673.5Q160,649 160,620L160,240Q160,157 237,118.5Q314,80 480,80Q652,80 726,117Q800,154 800,240L800,620Q800,649 789,673.5Q778,698 760,718L760,800Q760,817 748.5,828.5Q737,840 720,840L680,840Q663,840 651.5,828.5Q640,817 640,800L640,760L320,760L320,800Q320,817 308.5,828.5Q297,840 280,840L240,840ZM482,200Q592,200 641.5,200Q691,200 706,200L258,200Q276,200 325.5,200Q375,200 482,200ZM640,480L320,480Q287,480 263.5,480Q240,480 240,480L240,480L720,480L720,480Q720,480 696.5,480Q673,480 640,480ZM240,400L720,400L720,280L240,280L240,400ZM340,640Q365,640 382.5,622.5Q400,605 400,580Q400,555 382.5,537.5Q365,520 340,520Q315,520 297.5,537.5Q280,555 280,580Q280,605 297.5,622.5Q315,640 340,640ZM620,640Q645,640 662.5,622.5Q680,605 680,580Q680,555 662.5,537.5Q645,520 620,520Q595,520 577.5,537.5Q560,555 560,580Q560,605 577.5,622.5Q595,640 620,640ZM258,200L706,200Q691,183 641.5,171.5Q592,160 482,160Q375,160 325.5,172.5Q276,185 258,200ZM320,680L640,680Q673,680 696.5,656.5Q720,633 720,600L720,480L240,480L240,600Q240,633 263.5,656.5Q287,680 320,680Z"/>
</vector>

View File

@ -1,3 +1,6 @@
<resources>
<string name="app_name">MHD run Pathfinder</string>
<string name="routes_path">/v2/gtfs/routes</string>
<string name="stops_path">/v2/gtfs/stops</string>
<string name="golemio_base_url">https://api.golemio.cz</string>
</resources>