better Public transport vehicle map
This commit is contained in:
@@ -1,29 +1,37 @@
|
||||
package org.pupes.mhdrunpathfinder
|
||||
|
||||
import android.app.Activity
|
||||
import android.graphics.drawable.Icon
|
||||
import android.os.Handler
|
||||
import android.os.Looper
|
||||
import android.widget.Toast
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.foundation.layout.size
|
||||
import androidx.compose.material3.Icon
|
||||
import androidx.compose.material3.MaterialTheme
|
||||
import androidx.compose.material3.Surface
|
||||
import androidx.compose.material3.Text
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.draw.rotate
|
||||
import androidx.compose.ui.geometry.Offset
|
||||
import androidx.compose.ui.graphics.Color
|
||||
import androidx.compose.ui.res.painterResource
|
||||
import androidx.compose.ui.unit.dp
|
||||
import androidx.compose.ui.unit.sp
|
||||
import com.google.transit.realtime.GtfsRealtime
|
||||
import kotlinx.serialization.Serializable
|
||||
import okhttp3.OkHttpClient
|
||||
import okhttp3.Request
|
||||
import ovh.plrapps.mapcompose.api.addClusterer
|
||||
import ovh.plrapps.mapcompose.api.addMarker
|
||||
import ovh.plrapps.mapcompose.api.hasMarker
|
||||
import ovh.plrapps.mapcompose.api.moveMarker
|
||||
import ovh.plrapps.mapcompose.api.removeMarker
|
||||
import ovh.plrapps.mapcompose.api.visibleArea
|
||||
import ovh.plrapps.mapcompose.ui.state.markers.model.RenderingStrategy
|
||||
import java.net.URL
|
||||
import androidx.compose.foundation.layout.BoxScope
|
||||
import androidx.compose.foundation.layout.Box
|
||||
import androidx.compose.runtime.mutableStateMapOf
|
||||
|
||||
@Serializable
|
||||
data class GTFSRoute(
|
||||
@@ -88,6 +96,33 @@ val gtfshttpClient = OkHttpClient.Builder()
|
||||
private val GTFSMainHandler = Handler(Looper.getMainLooper())
|
||||
private var GTFSTrackingThread: Thread? = null
|
||||
private var GTFSIsTracking = false
|
||||
private val activeVehicleIds = mutableSetOf<String>()
|
||||
|
||||
private data class VehicleUiState(val bearing: Float, val routeShortName: String?)
|
||||
private val vehicleUiStates = mutableStateMapOf<String, VehicleUiState>()
|
||||
|
||||
private const val GTFS_CLUSTERER_ID = "gtfs_vehicles"
|
||||
private var gtfsClustererInitialized = false
|
||||
|
||||
private fun ensureGtfsClusterer() {
|
||||
if (gtfsClustererInitialized) return
|
||||
mapState.addClusterer(GTFS_CLUSTERER_ID, 30.dp) { ids ->
|
||||
{
|
||||
Surface(
|
||||
shape = MaterialTheme.shapes.small,
|
||||
color = Color(0xFF31FF00)
|
||||
) {
|
||||
Text(
|
||||
text = "${ids.size}",
|
||||
modifier = Modifier.padding(4.dp),
|
||||
fontSize = 11.sp,
|
||||
color = Color.Black
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
gtfsClustererInitialized = true
|
||||
}
|
||||
|
||||
private fun fetchGTFSPositions(gtfsUrl: URL, api_key: String, callee: Activity) {
|
||||
try {
|
||||
@@ -102,35 +137,84 @@ private fun fetchGTFSPositions(gtfsUrl: URL, api_key: String, callee: Activity)
|
||||
throw Exception("Error: ${response.code} ${response.message}")
|
||||
}
|
||||
|
||||
val bytes = response.body?.bytes() ?: throw Exception("Empty response")
|
||||
val feed = GtfsRealtime.FeedMessage.parseFrom(bytes)
|
||||
val feed = response.body?.byteStream()?.use { stream ->
|
||||
GtfsRealtime.FeedMessage.parseFrom(stream)
|
||||
} ?: throw Exception("Empty response")
|
||||
|
||||
data class VehicleUpdate(
|
||||
val id: String,
|
||||
val x: Double,
|
||||
val y: Double,
|
||||
val bearing: Float,
|
||||
val routeShortName: String?
|
||||
)
|
||||
|
||||
val seenIds = mutableSetOf<String>()
|
||||
val updates = mutableListOf<VehicleUpdate>()
|
||||
|
||||
feed.entityList.forEach { entity ->
|
||||
if (entity.hasVehicle()) {
|
||||
GTFSMainHandler.post {
|
||||
|
||||
if(mapState.hasMarker(entity.vehicle.vehicle.id)) {
|
||||
mapState.moveMarker(entity.vehicle.vehicle.id, x = longitudeToXNormalized(entity.vehicle.position.longitude.toDouble()), y = latitudeToYNormalized(entity.vehicle.position.latitude.toDouble()));
|
||||
|
||||
} else {
|
||||
mapState.addMarker(
|
||||
entity.vehicle.vehicle.id,
|
||||
val id = entity.vehicle.vehicle.id
|
||||
seenIds.add(id)
|
||||
val routeId = entity.vehicle.trip.routeId
|
||||
updates.add(
|
||||
VehicleUpdate(
|
||||
id = id,
|
||||
x = longitudeToXNormalized(entity.vehicle.position.longitude.toDouble()),
|
||||
y = latitudeToYNormalized(entity.vehicle.position.latitude.toDouble()),
|
||||
Offset(-0.5f, -0.5f)
|
||||
bearing = entity.vehicle.position.bearing,
|
||||
routeShortName = gtfsRoutes.find { it.route_id == routeId }?.route_short_name
|
||||
)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
GTFSMainHandler.post {
|
||||
ensureGtfsClusterer()
|
||||
updates.forEach { u ->
|
||||
vehicleUiStates[u.id] = VehicleUiState(u.bearing, u.routeShortName)
|
||||
|
||||
if (mapState.hasMarker(u.id)) {
|
||||
mapState.moveMarker(u.id, x = u.x, y = u.y)
|
||||
} else {
|
||||
mapState.addMarker(
|
||||
u.id,
|
||||
x = u.x,
|
||||
y = u.y,
|
||||
Offset(-0.5f, -0.5f),
|
||||
renderingStrategy = RenderingStrategy.Clustering(GTFS_CLUSTERER_ID)
|
||||
) {
|
||||
val state = vehicleUiStates[u.id]
|
||||
Box(
|
||||
modifier = Modifier.size(28.dp),
|
||||
contentAlignment = Alignment.Center
|
||||
) {
|
||||
Icon(
|
||||
painter = painterResource(id = R.drawable.mhd_icon),
|
||||
contentDescription = null,
|
||||
modifier = Modifier.size(14.dp).rotate(entity.vehicle.position.bearing),
|
||||
modifier = Modifier.size(16.dp).rotate(state?.bearing ?: u.bearing),
|
||||
tint = Color(0xFF31FF00)
|
||||
)
|
||||
// Text("${(gtfsRoutes.find {it.route_id == entity.tripUpdate.trip.routeId})?.route_short_name}")
|
||||
Text(
|
||||
"${state?.routeShortName ?: u.routeShortName}",
|
||||
color = Color.Red,
|
||||
fontSize = 10.sp,
|
||||
modifier = Modifier.align(Alignment.Center)
|
||||
)
|
||||
}
|
||||
}
|
||||
activeVehicleIds.add(u.id)
|
||||
}
|
||||
// use vehicle
|
||||
}
|
||||
|
||||
|
||||
val stale = activeVehicleIds - seenIds
|
||||
stale.forEach { staleId ->
|
||||
mapState.removeMarker(staleId)
|
||||
vehicleUiStates.remove(staleId)
|
||||
}
|
||||
activeVehicleIds.removeAll(stale)
|
||||
activeVehicleIds.addAll(seenIds)
|
||||
}
|
||||
}
|
||||
} catch (e: Exception) {
|
||||
@@ -248,4 +332,7 @@ fun stopGTFSTracking() {
|
||||
}
|
||||
GTFSTrackingThread?.interrupt()
|
||||
GTFSTrackingThread = null
|
||||
activeVehicleIds.clear()
|
||||
vehicleUiStates.clear()
|
||||
gtfsClustererInitialized = false
|
||||
}
|
||||
Reference in New Issue
Block a user