Compare commits
No commits in common. "4263a7eff027ca32f4f20d6c3aa5241f4b22d1a3" and "ca2c5fe38506ee48cb85c3773badb34700548cf3" have entirely different histories.
4263a7eff0
...
ca2c5fe385
1
.gitignore
vendored
1
.gitignore
vendored
@ -13,4 +13,3 @@
|
|||||||
.externalNativeBuild
|
.externalNativeBuild
|
||||||
.cxx
|
.cxx
|
||||||
local.properties
|
local.properties
|
||||||
test-data
|
|
||||||
|
|||||||
4
.idea/deploymentTargetSelector.xml
generated
4
.idea/deploymentTargetSelector.xml
generated
@ -4,10 +4,10 @@
|
|||||||
<selectionStates>
|
<selectionStates>
|
||||||
<SelectionState runConfigName="app">
|
<SelectionState runConfigName="app">
|
||||||
<option name="selectionMode" value="DROPDOWN" />
|
<option name="selectionMode" value="DROPDOWN" />
|
||||||
<DropdownSelection timestamp="2026-01-28T21:00:38.307279245Z">
|
<DropdownSelection timestamp="2026-01-19T17:27:16.298528612Z">
|
||||||
<Target type="DEFAULT_BOOT">
|
<Target type="DEFAULT_BOOT">
|
||||||
<handle>
|
<handle>
|
||||||
<DeviceId pluginId="PhysicalDevice" identifier="serial=BS98317AA1341400032" />
|
<DeviceId pluginId="LocalEmulator" identifier="path=/home/lukas/.config/.android/avd/Medium_Phone_OSS.avd" />
|
||||||
</handle>
|
</handle>
|
||||||
</Target>
|
</Target>
|
||||||
</DropdownSelection>
|
</DropdownSelection>
|
||||||
|
|||||||
@ -14,36 +14,33 @@ import kotlinx.serialization.Serializable
|
|||||||
import okhttp3.OkHttpClient
|
import okhttp3.OkHttpClient
|
||||||
import okhttp3.Request
|
import okhttp3.Request
|
||||||
import ovh.plrapps.mapcompose.api.addMarker
|
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
|
import java.net.URL
|
||||||
|
|
||||||
@Serializable
|
@Serializable
|
||||||
data class GTFSRoute(
|
data class GTFSRoute(
|
||||||
val agency_id: String?,
|
val agency_id: String,
|
||||||
val is_night: Boolean,
|
val is_night: Boolean,
|
||||||
val route_color: String?,
|
val route_color: String,
|
||||||
val route_desc: String? = null,
|
val route_desc: String? = null,
|
||||||
val route_id: String?,
|
val route_id: String,
|
||||||
val route_long_name: String?,
|
val route_long_name: String,
|
||||||
val route_short_name: String?,
|
val route_short_name: String,
|
||||||
val route_text_color: String?,
|
val route_text_color: String,
|
||||||
val route_type: Int,
|
val route_type: Int,
|
||||||
val route_url: String?,
|
val route_url: String,
|
||||||
val is_regional: Boolean,
|
val is_regional: Boolean,
|
||||||
val is_substitute_transport: Boolean
|
val is_substitute_transport: Boolean
|
||||||
)
|
)
|
||||||
|
|
||||||
// Type alias for array of GTFS routes
|
// Type alias for array of GTFS routes
|
||||||
typealias GTFSRoutes = MutableList<GTFSRoute>
|
typealias GTFSRoutes = List<GTFSRoute>
|
||||||
|
|
||||||
var gtfsRoutes: GTFSRoutes = mutableListOf()
|
|
||||||
|
|
||||||
|
var gtfsRoutes: GTFSRoutes? = null
|
||||||
|
|
||||||
@Serializable
|
@Serializable
|
||||||
data class GTFSStopGeometry(
|
data class GTFSStopGeometry(
|
||||||
val coordinates: List<Double>,
|
val coordinates: List<Double>,
|
||||||
val type: String?
|
val type: String
|
||||||
)
|
)
|
||||||
|
|
||||||
@Serializable
|
@Serializable
|
||||||
@ -52,7 +49,7 @@ data class GTFSStopProperties(
|
|||||||
val parent_station: String? = null,
|
val parent_station: String? = null,
|
||||||
val platform_code: String? = null,
|
val platform_code: String? = null,
|
||||||
val stop_id: String,
|
val stop_id: String,
|
||||||
val stop_name: String?,
|
val stop_name: String,
|
||||||
val wheelchair_boarding: Int,
|
val wheelchair_boarding: Int,
|
||||||
val zone_id: String? = null,
|
val zone_id: String? = null,
|
||||||
val level_id: String? = null
|
val level_id: String? = null
|
||||||
@ -62,16 +59,16 @@ data class GTFSStopProperties(
|
|||||||
data class GTFSStop(
|
data class GTFSStop(
|
||||||
val geometry: GTFSStopGeometry,
|
val geometry: GTFSStopGeometry,
|
||||||
val properties: GTFSStopProperties,
|
val properties: GTFSStopProperties,
|
||||||
val type: String?
|
val type: String
|
||||||
)
|
)
|
||||||
|
|
||||||
@Serializable
|
@Serializable
|
||||||
data class GTFSStops(
|
data class GTFSStops(
|
||||||
val features: MutableList<GTFSStop>,
|
val features: List<GTFSStop>,
|
||||||
val type: String?
|
val type: String
|
||||||
)
|
)
|
||||||
|
|
||||||
var gtfsStops: GTFSStops = GTFSStops(mutableListOf<GTFSStop>(), "FeatureCollection")
|
var gtfsStops: GTFSStops? = null
|
||||||
|
|
||||||
|
|
||||||
// OkHttpClient for making HTTP requests
|
// OkHttpClient for making HTTP requests
|
||||||
@ -107,24 +104,17 @@ private fun fetchGTFSPosition(gtfsUrl: URL,api_key: String, callee: Activity) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
// Start the timer with a URL:
|
// Start the timer with a URL:
|
||||||
fun startGTFSTracking(url: String, api_key: String, callee: Activity) {
|
fun startGTFSTracking(url: String, api_key: String, callee: Activity) {
|
||||||
if (GTFSIsTracking) return
|
if (GTFSIsTracking) return
|
||||||
|
|
||||||
GTFSIsTracking = true
|
GTFSIsTracking = true
|
||||||
GTFSTrackingThread = Thread {
|
GTFSTrackingThread = Thread {
|
||||||
val gtfsUrl = URL(url);
|
var gtfsUrl = URL(url);
|
||||||
try {
|
try {
|
||||||
if(gtfsRoutes.isEmpty()) {
|
if(gtfsRoutes == null) {
|
||||||
GTFSMainHandler.post {
|
|
||||||
Toast.makeText(callee, "Fetching routes...", Toast.LENGTH_SHORT).show()}
|
|
||||||
|
|
||||||
var messageParsed: GTFSRoutes;
|
|
||||||
var offset = 0;
|
|
||||||
do {
|
|
||||||
val request = Request.Builder()
|
val request = Request.Builder()
|
||||||
.url("${gtfsUrl.protocol}://${gtfsUrl.host}${callee.getString(R.string.routes_path)}?offset=${offset}")
|
.url("${gtfsUrl.protocol}://${gtfsUrl.host}${callee.getString(R.string.routes_path)}")
|
||||||
.header(
|
.header(
|
||||||
"User-Agent",
|
"User-Agent",
|
||||||
"${BuildConfig.APPLICATION_ID}/${BuildConfig.VERSION_NAME} (Android)"
|
"${BuildConfig.APPLICATION_ID}/${BuildConfig.VERSION_NAME} (Android)"
|
||||||
@ -137,20 +127,11 @@ fun startGTFSTracking(url: String, api_key: String, callee: Activity) {
|
|||||||
} else {
|
} else {
|
||||||
"Error: ${response.code} ${response.message}"
|
"Error: ${response.code} ${response.message}"
|
||||||
}
|
}
|
||||||
messageParsed = json.decodeFromString<GTFSRoutes>(message)
|
gtfsRoutes = json.decodeFromString<GTFSRoutes>(message)
|
||||||
gtfsRoutes.addAll(messageParsed)
|
}}
|
||||||
offset += messageParsed.size
|
if(gtfsStops == null) {
|
||||||
}} 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()
|
val request = Request.Builder()
|
||||||
.url("${gtfsUrl.protocol}://${gtfsUrl.host}${callee.getString(R.string.stops_path)}?offset=${offset}")
|
.url("${gtfsUrl.protocol}://${gtfsUrl.host}${callee.getString(R.string.stops_path)}")
|
||||||
.header(
|
.header(
|
||||||
"User-Agent",
|
"User-Agent",
|
||||||
"${BuildConfig.APPLICATION_ID}/${BuildConfig.VERSION_NAME} (Android)"
|
"${BuildConfig.APPLICATION_ID}/${BuildConfig.VERSION_NAME} (Android)"
|
||||||
@ -163,35 +144,25 @@ fun startGTFSTracking(url: String, api_key: String, callee: Activity) {
|
|||||||
} else {
|
} else {
|
||||||
"Error: ${response.code} ${response.message}"
|
"Error: ${response.code} ${response.message}"
|
||||||
}
|
}
|
||||||
messageParsed = json.decodeFromString<GTFSStops>(message)
|
gtfsStops = json.decodeFromString<GTFSStops>(message)
|
||||||
gtfsStops.features.addAll(messageParsed.features)
|
|
||||||
}
|
|
||||||
|
|
||||||
offset += messageParsed.features.size;
|
|
||||||
}while(messageParsed.features.isNotEmpty())
|
|
||||||
}
|
|
||||||
// put all stops on the map
|
// put all stops on the map
|
||||||
GTFSMainHandler.post {
|
|
||||||
Toast.makeText(callee, "Drawing stops...", Toast.LENGTH_SHORT).show()}
|
|
||||||
for (stop in gtfsStops!!.features) {
|
for (stop in gtfsStops!!.features) {
|
||||||
|
GTFSMainHandler.post {
|
||||||
if (stop.properties.parent_station == null) {
|
mapState.addMarker(stop.properties.stop_id, x = longitudeToXNormalized(stop.geometry.coordinates[0]), y = latitudeToYNormalized(stop.geometry.coordinates[0])) {
|
||||||
mapState.addMarker(stop.properties.stop_id, x = longitudeToXNormalized(stop.geometry.coordinates[0]), y = latitudeToYNormalized(stop.geometry.coordinates[1]), renderingStrategy = RenderingStrategy.LazyLoading("default")) {
|
|
||||||
Icon(
|
Icon(
|
||||||
painter = painterResource(id = R.drawable.outline_directions_bus_24),
|
painter = painterResource(id = R.drawable.outline_directions_bus_24),
|
||||||
contentDescription = null,
|
contentDescription = null,
|
||||||
modifier = Modifier.size(12.dp),
|
modifier = Modifier.size(17.dp),
|
||||||
tint = Color(0xFF0000FF)
|
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()
|
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
} catch(e: Exception) {
|
||||||
|
Toast.makeText(callee, e.message ?: "Unknown error", Toast.LENGTH_SHORT).show()
|
||||||
GTFSMainHandler.post { stopGTFSTracking() }
|
GTFSMainHandler.post { stopGTFSTracking() }
|
||||||
return@Thread
|
return@Thread
|
||||||
}
|
}
|
||||||
@ -208,11 +179,6 @@ fun startGTFSTracking(url: String, api_key: String, callee: Activity) {
|
|||||||
|
|
||||||
fun stopGTFSTracking() {
|
fun stopGTFSTracking() {
|
||||||
GTFSIsTracking = false
|
GTFSIsTracking = false
|
||||||
for (stop in gtfsStops!!.features) {
|
|
||||||
if (stop.properties.parent_station == null) {
|
|
||||||
mapState.removeMarker(stop.properties.stop_id);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
GTFSTrackingThread?.interrupt()
|
GTFSTrackingThread?.interrupt()
|
||||||
GTFSTrackingThread = null
|
GTFSTrackingThread = null
|
||||||
}
|
}
|
||||||
@ -40,25 +40,14 @@ import androidx.compose.ui.tooling.preview.Preview
|
|||||||
import androidx.compose.ui.unit.dp
|
import androidx.compose.ui.unit.dp
|
||||||
import androidx.core.content.edit
|
import androidx.core.content.edit
|
||||||
import ovh.plrapps.mapcompose.api.addLayer
|
import ovh.plrapps.mapcompose.api.addLayer
|
||||||
import ovh.plrapps.mapcompose.api.addLazyLoader
|
|
||||||
import ovh.plrapps.mapcompose.api.addMarker
|
import ovh.plrapps.mapcompose.api.addMarker
|
||||||
import ovh.plrapps.mapcompose.api.scrollTo
|
import ovh.plrapps.mapcompose.api.scrollTo
|
||||||
import ovh.plrapps.mapcompose.core.TileStreamProvider
|
import ovh.plrapps.mapcompose.core.TileStreamProvider
|
||||||
import ovh.plrapps.mapcompose.ui.MapUI
|
import ovh.plrapps.mapcompose.ui.MapUI
|
||||||
import ovh.plrapps.mapcompose.ui.state.MapState
|
import ovh.plrapps.mapcompose.ui.state.MapState
|
||||||
import java.io.File
|
|
||||||
import java.io.FileInputStream
|
|
||||||
import java.io.FileOutputStream
|
|
||||||
import java.net.URL
|
import java.net.URL
|
||||||
import kotlinx.coroutines.*
|
|
||||||
import java.util.concurrent.Executors
|
|
||||||
import android.os.StrictMode
|
|
||||||
|
|
||||||
|
|
||||||
// Thread pool for tile downloading to prevent blocking UI thread
|
|
||||||
private val tileDownloadExecutor = Executors.newFixedThreadPool(
|
|
||||||
(Runtime.getRuntime().availableProcessors() - 3).coerceAtLeast(1)
|
|
||||||
)
|
|
||||||
// MapState configuration for OpenStreetMap
|
// MapState configuration for OpenStreetMap
|
||||||
// Max zoom level is 18, tile size is 256x256
|
// Max zoom level is 18, tile size is 256x256
|
||||||
// At zoom level 18, there are 2^18 = 262144 tiles in each dimension
|
// At zoom level 18, there are 2^18 = 262144 tiles in each dimension
|
||||||
@ -70,108 +59,13 @@ val mapState = MapState(
|
|||||||
tileSize = 256
|
tileSize = 256
|
||||||
)
|
)
|
||||||
|
|
||||||
/**
|
|
||||||
* Creates a cached tile stream provider for OpenStreetMap tiles
|
|
||||||
* Tiles are cached in the app's cache directory for offline access and faster loading
|
|
||||||
* Cache expires after 30 days to ensure map updates are fetched
|
|
||||||
* Uses background thread pool to prevent ANR (Application Not Responding)
|
|
||||||
*/
|
|
||||||
fun createCachedTileStreamProvider(context: Context, cacheExpiryDays: Int = 30): TileStreamProvider {
|
|
||||||
val cacheDir = File(context.cacheDir, "map_tiles")
|
|
||||||
if (!cacheDir.exists()) {
|
|
||||||
cacheDir.mkdirs()
|
|
||||||
}
|
|
||||||
|
|
||||||
val cacheExpiryMillis = cacheExpiryDays * 24 * 60 * 60 * 1000L
|
|
||||||
|
|
||||||
return TileStreamProvider { row, col, zoomLvl ->
|
|
||||||
try {
|
|
||||||
// Create a unique filename for this tile
|
|
||||||
val tileFile = File(cacheDir, "tile_${zoomLvl}_${col}_${row}.png")
|
|
||||||
|
|
||||||
// Check if tile exists in cache and is not expired
|
|
||||||
val shouldUseCache = tileFile.exists() &&
|
|
||||||
(System.currentTimeMillis() - tileFile.lastModified()) < cacheExpiryMillis
|
|
||||||
|
|
||||||
if (shouldUseCache) {
|
|
||||||
// Use cached tile
|
|
||||||
FileInputStream(tileFile)
|
|
||||||
} else {
|
|
||||||
// Download tile from OpenStreetMap in background thread
|
|
||||||
val future = tileDownloadExecutor.submit<FileInputStream?> {
|
|
||||||
try {
|
|
||||||
val url = URL("https://tile.openstreetmap.org/$zoomLvl/$col/$row.png")
|
|
||||||
val connection = url.openConnection()
|
|
||||||
// Set User-Agent header as required by OSM tile usage policy
|
|
||||||
val userAgent = "${BuildConfig.APPLICATION_ID}/${BuildConfig.VERSION_NAME} (Android) (Contact: poliecho@pupes.org)"
|
|
||||||
connection.setRequestProperty("User-Agent", userAgent)
|
|
||||||
connection.connectTimeout = 5000 // 5 second timeout
|
|
||||||
connection.readTimeout = 10000 // 10 second timeout
|
|
||||||
|
|
||||||
val inputStream = connection.getInputStream()
|
|
||||||
|
|
||||||
// Save to cache (overwrites if expired)
|
|
||||||
val outputStream = FileOutputStream(tileFile)
|
|
||||||
inputStream.use { input ->
|
|
||||||
outputStream.use { output ->
|
|
||||||
input.copyTo(output)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Return the cached file's input stream
|
|
||||||
FileInputStream(tileFile)
|
|
||||||
} catch (e: Exception) {
|
|
||||||
e.printStackTrace()
|
|
||||||
null
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Wait for download with timeout
|
|
||||||
try {
|
|
||||||
future.get() ?: run {
|
|
||||||
// If download failed but we have a cached tile (even if expired), use it
|
|
||||||
if (tileFile.exists()) {
|
|
||||||
FileInputStream(tileFile)
|
|
||||||
} else {
|
|
||||||
null
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} catch (e: Exception) {
|
|
||||||
// If download timed out or failed, try using expired cache
|
|
||||||
if (tileFile.exists()) {
|
|
||||||
FileInputStream(tileFile)
|
|
||||||
} else {
|
|
||||||
null
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} catch (e: Exception) {
|
|
||||||
e.printStackTrace()
|
|
||||||
null
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
class MainActivity : ComponentActivity() {
|
class MainActivity : ComponentActivity() {
|
||||||
override fun onCreate(savedInstanceState: Bundle?) {
|
override fun onCreate(savedInstanceState: Bundle?) {
|
||||||
super.onCreate(savedInstanceState)
|
super.onCreate(savedInstanceState)
|
||||||
|
|
||||||
// Configure StrictMode to detect threading violations (debug mode only)
|
|
||||||
if (BuildConfig.DEBUG) {
|
|
||||||
StrictMode.setThreadPolicy(
|
|
||||||
StrictMode.ThreadPolicy.Builder()
|
|
||||||
.detectAll()
|
|
||||||
.penaltyLog() // Log violations instead of crashing
|
|
||||||
.build()
|
|
||||||
)
|
|
||||||
StrictMode.setVmPolicy(
|
|
||||||
StrictMode.VmPolicy.Builder()
|
|
||||||
.detectAll()
|
|
||||||
.penaltyLog()
|
|
||||||
.build()
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
setContent {
|
setContent {
|
||||||
MaterialTheme {
|
MaterialTheme {
|
||||||
@ -201,16 +95,15 @@ fun MainScreen(callee: Activity) {
|
|||||||
var haukUrl by remember { mutableStateOf("") }
|
var haukUrl by remember { mutableStateOf("") }
|
||||||
var golemioAPIkey by remember { mutableStateOf(sharedPreferences.getString("golemioAPIkey", "")?: "" ) }
|
var golemioAPIkey by remember { mutableStateOf(sharedPreferences.getString("golemioAPIkey", "")?: "" ) }
|
||||||
var haukState by remember { mutableStateOf(false) }
|
var haukState by remember { mutableStateOf(false) }
|
||||||
var golemioState by remember { mutableStateOf(golemioAPIkey.isNotEmpty())}
|
var golemioState by remember { mutableStateOf(false)}
|
||||||
|
|
||||||
LaunchedEffect(Unit) {
|
if (golemioAPIkey.isNotEmpty()) {
|
||||||
if (golemioState) {
|
golemioState = true
|
||||||
startGTFSTracking(callee.getString(R.string.golemio_base_url), golemioAPIkey, callee)
|
startGTFSTracking(callee.getString(R.string.golemio_base_url), golemioAPIkey, callee);
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
Box(modifier = Modifier.fillMaxSize()) {
|
Box(modifier = Modifier.fillMaxSize()) {
|
||||||
OpenStreetMapScreen(callee)
|
OpenStreetMapScreen()
|
||||||
SettingsButton(
|
SettingsButton(
|
||||||
onClick = { showSettings = true },
|
onClick = { showSettings = true },
|
||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
@ -318,7 +211,6 @@ fun ShowSettingsMenu(
|
|||||||
value = haukUrl,
|
value = haukUrl,
|
||||||
onValueChange = onHaukUrlChange,
|
onValueChange = onHaukUrlChange,
|
||||||
label = { Text("Target Hauk URL") },
|
label = { Text("Target Hauk URL") },
|
||||||
singleLine = true,
|
|
||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
.width(250.dp)
|
.width(250.dp)
|
||||||
)
|
)
|
||||||
@ -332,7 +224,6 @@ fun ShowSettingsMenu(
|
|||||||
onCheckedChange = { onGolemioStateChange(it) }
|
onCheckedChange = { onGolemioStateChange(it) }
|
||||||
)
|
)
|
||||||
TextField(value = golemioAPIkey, onValueChange = onGolemioAPIChange, label = { Text("Golemio API Key") },
|
TextField(value = golemioAPIkey, onValueChange = onGolemioAPIChange, label = { Text("Golemio API Key") },
|
||||||
singleLine = true,
|
|
||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
.width(250.dp));
|
.width(250.dp));
|
||||||
ElevatedButton(onClick = onGolemioApply, modifier = Modifier.padding(start = 5.dp), shape = RectangleShape) {
|
ElevatedButton(onClick = onGolemioApply, modifier = Modifier.padding(start = 5.dp), shape = RectangleShape) {
|
||||||
@ -344,17 +235,15 @@ fun ShowSettingsMenu(
|
|||||||
|
|
||||||
@Preview
|
@Preview
|
||||||
@Composable
|
@Composable
|
||||||
fun OpenStreetMapScreen(context: Context? = null) {
|
fun OpenStreetMapScreen() {
|
||||||
// Create tile stream provider for OpenStreetMap with caching
|
// Create tile stream provider for OpenStreetMap with User-Agent
|
||||||
val tileStreamProvider = remember(context) {
|
val tileStreamProvider = TileStreamProvider { row, col, zoomLvl ->
|
||||||
context?.let {
|
|
||||||
createCachedTileStreamProvider(it)
|
|
||||||
} ?: TileStreamProvider { row, col, zoomLvl ->
|
|
||||||
// Fallback for preview mode without context
|
|
||||||
try {
|
try {
|
||||||
val url = URL("https://tile.openstreetmap.org/$zoomLvl/$col/$row.png")
|
val url = URL("https://tile.openstreetmap.org/$zoomLvl/$col/$row.png")
|
||||||
val connection = url.openConnection()
|
val connection = url.openConnection()
|
||||||
val userAgent = "MHDrunPathfinder/Preview (Android)"
|
// Set User-Agent header as required by OSM tile usage policy
|
||||||
|
// Format: AppId/Version (Platform) (Contact: email)
|
||||||
|
val userAgent = "${BuildConfig.APPLICATION_ID}/${BuildConfig.VERSION_NAME} (Android) (Contact: poliecho@pupes.org)"
|
||||||
connection.setRequestProperty("User-Agent", userAgent)
|
connection.setRequestProperty("User-Agent", userAgent)
|
||||||
connection.getInputStream()
|
connection.getInputStream()
|
||||||
} catch (e: Exception) {
|
} catch (e: Exception) {
|
||||||
@ -362,9 +251,8 @@ fun OpenStreetMapScreen(context: Context? = null) {
|
|||||||
null
|
null
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
mapState.addLazyLoader("default");
|
|
||||||
|
|
||||||
/* Add a marker at the center of the map */
|
/* Add a marker at the center of the map */
|
||||||
mapState.addMarker("target_position", x = longitudeToXNormalized(14.4058031), y = latitudeToYNormalized(50.0756083)) {
|
mapState.addMarker("target_position", x = longitudeToXNormalized(14.4058031), y = latitudeToYNormalized(50.0756083)) {
|
||||||
|
|||||||
@ -3,5 +3,4 @@
|
|||||||
<string name="routes_path">/v2/gtfs/routes</string>
|
<string name="routes_path">/v2/gtfs/routes</string>
|
||||||
<string name="stops_path">/v2/gtfs/stops</string>
|
<string name="stops_path">/v2/gtfs/stops</string>
|
||||||
<string name="golemio_base_url">https://api.golemio.cz</string>
|
<string name="golemio_base_url">https://api.golemio.cz</string>
|
||||||
<string name="golemio_realtime_path"></string>
|
|
||||||
</resources>
|
</resources>
|
||||||
Loading…
x
Reference in New Issue
Block a user