GeolocationInfo.kt 3.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123
  1. package de.chst.ghostrider.presentation
  2. import android.util.Log
  3. import org.w3c.dom.Element
  4. import java.io.InputStream
  5. import java.lang.IllegalArgumentException
  6. import java.time.Duration
  7. import java.time.Instant
  8. import java.time.LocalTime
  9. import javax.xml.parsers.DocumentBuilderFactory
  10. data class Route(val timeMilliseconds: Array<Long>, val lat: Array<Float>, val lon: Array<Float>) {
  11. fun size(): Int {
  12. return timeMilliseconds.size
  13. }
  14. fun lengthSeconds(): Long {
  15. assert(timeMilliseconds.isNotEmpty())
  16. return timeMilliseconds[timeMilliseconds.size - 1] / 1000
  17. }
  18. }
  19. fun load_gpx(input: InputStream): Route {
  20. val factory = DocumentBuilderFactory.newInstance()
  21. val builder = factory.newDocumentBuilder()
  22. val document = builder.parse(input)
  23. val trkpts = document.getElementsByTagName("trkpt")
  24. var lats: Array<Float> = Array(trkpts.length) { 0f }
  25. var longs: Array<Float> = Array(trkpts.length) { 0f }
  26. var timeMilliseconds: Array<Long> = Array(trkpts.length) { 0L }
  27. var startTime: Instant? = null
  28. for (i in 0 until trkpts.length) {
  29. val trkpt = trkpts.item(i) as Element
  30. lats[i] = trkpt.getAttribute("lat").toFloat()
  31. longs[i] = trkpt.getAttribute("lon").toFloat()
  32. val timeTags = trkpt.getElementsByTagName("time")
  33. if(timeTags.length == 0) {
  34. throw IllegalArgumentException("GPX does not contain time informationm")
  35. }
  36. val time = Instant.parse((timeTags.item(0) as Element).textContent)
  37. if (i == 0) {
  38. timeMilliseconds[i] = 0
  39. startTime = time
  40. } else {
  41. val dur = Duration.between(startTime, time)
  42. timeMilliseconds[i] = dur.toMillis()
  43. }
  44. }
  45. return Route(timeMilliseconds, lats, longs)
  46. }
  47. fun deg2rad(degrees: Float): Double {
  48. return degrees * Math.PI / 180f
  49. }
  50. fun geoDistance(lat1: Float, lon1: Float, lat2: Float, lon2: Float): Float {
  51. // adapted from https://stackoverflow.com/a/27943
  52. val R = 6371; // Radius of the earth in km
  53. val dLat = deg2rad(lat2-lat1); // deg2rad below
  54. val dLon = deg2rad(lon2-lon1);
  55. val a =
  56. Math.sin(dLat/2) * Math.sin(dLat/2) +
  57. Math.cos(deg2rad(lat1)) * Math.cos(deg2rad(lat2)) *
  58. Math.sin(dLon/2) * Math.sin(dLon/2)
  59. ;
  60. val c = 2 * Math.atan2(Math.sqrt(a), Math.sqrt(1-a));
  61. val d = R * c * 1000; // Distance in m
  62. return d.toFloat();
  63. }
  64. fun find_closest_point(route: Route, lat: Float, lon: Float): Pair<Int, Float> {
  65. var minIndex: Int = 0
  66. var distance: Float = Float.POSITIVE_INFINITY
  67. for (i in 0 until route.timeMilliseconds.size) {
  68. val newDistance = geoDistance(lat, lon, route.lat[i], route.lon[i])
  69. if (newDistance < distance) {
  70. minIndex = i
  71. distance = newDistance
  72. }
  73. }
  74. return Pair(minIndex, distance)
  75. }
  76. /**
  77. * Given a route, the milliseconds since planned start of transit
  78. * and the current position, calculate
  79. * the relative time to the scheduled route in milliseconds.
  80. * If we are ahead, returns a negative number, if we are behind, returns a positive number.
  81. *
  82. * If the position is not close enough, returns null.
  83. */
  84. fun calculate_relative_time(route: Route,
  85. routeTime: Int,
  86. timeInTransit: Long,
  87. lat: Float, lon: Float): Long? {
  88. val (closestTrackpointIndex, distance) = find_closest_point(route, lat, lon)
  89. val routeTimeFactor = routeTime.toFloat() / route.lengthSeconds().toFloat()
  90. val plannedDeltaTime = route.timeMilliseconds[closestTrackpointIndex] * routeTimeFactor
  91. val delay = if (distance > 60) {
  92. null // if not close enough we can not determine the delay reliably.
  93. } else {
  94. plannedDeltaTime - timeInTransit
  95. }
  96. Log.d("de.chst.GhostRider", "Distance to closest point: ${distance}m, plannedTime: $plannedDeltaTime, delay: $delay")
  97. return delay?.toLong()
  98. }