218 lines
7.7 KiB
Kotlin

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 ovh.plrapps.mapcompose.api.removeMarker
import ovh.plrapps.mapcompose.ui.state.markers.model.RenderingStrategy
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 = MutableList<GTFSRoute>
var gtfsRoutes: GTFSRoutes = mutableListOf()
@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: MutableList<GTFSStop>,
val type: String?
)
var gtfsStops: GTFSStops = GTFSStops(mutableListOf<GTFSStop>(), "FeatureCollection")
// 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 {
val gtfsUrl = URL(url);
try {
if(gtfsRoutes.isEmpty()) {
GTFSMainHandler.post {
Toast.makeText(callee, "Fetching routes...", Toast.LENGTH_SHORT).show()}
var messageParsed: GTFSRoutes;
var offset = 0;
do {
val request = Request.Builder()
.url("${gtfsUrl.protocol}://${gtfsUrl.host}${callee.getString(R.string.routes_path)}?offset=${offset}")
.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}"
}
messageParsed = json.decodeFromString<GTFSRoutes>(message)
gtfsRoutes.addAll(messageParsed)
offset += messageParsed.size
}} while (messageParsed.isNotEmpty())
}
if(gtfsStops.features.isEmpty()) {
GTFSMainHandler.post {
Toast.makeText(callee, "Fetching stops...", Toast.LENGTH_SHORT).show()}
var messageParsed: GTFSStops;
var offset = 0;
do {
val request = Request.Builder()
.url("${gtfsUrl.protocol}://${gtfsUrl.host}${callee.getString(R.string.stops_path)}?offset=${offset}")
.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}"
}
messageParsed = json.decodeFromString<GTFSStops>(message)
gtfsStops.features.addAll(messageParsed.features)
}
offset += messageParsed.features.size;
}while(messageParsed.features.isNotEmpty())
}
// put all stops on the map
GTFSMainHandler.post {
Toast.makeText(callee, "Drawing stops...", Toast.LENGTH_SHORT).show()}
for (stop in gtfsStops!!.features) {
if (stop.properties.parent_station == null) {
mapState.addMarker(stop.properties.stop_id, x = longitudeToXNormalized(stop.geometry.coordinates[0]), y = latitudeToYNormalized(stop.geometry.coordinates[1]), renderingStrategy = RenderingStrategy.LazyLoading("default")) {
Icon(
painter = painterResource(id = R.drawable.outline_directions_bus_24),
contentDescription = null,
modifier = Modifier.size(12.dp),
tint = Color(0xFF0000FF)
)
}}
}
GTFSMainHandler.post {
Toast.makeText(callee, "Done drawing stops", Toast.LENGTH_SHORT).show()}
} catch(e: Exception) {
GTFSMainHandler.post {
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
for (stop in gtfsStops!!.features) {
if (stop.properties.parent_station == null) {
mapState.removeMarker(stop.properties.stop_id);
}
}
GTFSTrackingThread?.interrupt()
GTFSTrackingThread = null
}