RouteFinder.kt 16 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334
  1. package ml.adamsprogs.bimba
  2. import android.content.Context
  3. import android.database.sqlite.SQLiteDatabase
  4. import android.database.sqlite.SQLiteException
  5. import ml.adamsprogs.bimba.models.Timetable
  6. import ml.adamsprogs.bimba.models.gtfs.Calendar
  7. import java.io.File
  8. import java.util.*
  9. import java.util.Calendar as JCalendar
  10. class RouteFinder {
  11. companion object {
  12. private lateinit var timetable: Timetable
  13. private const val tramSpeed = 20 //heuristic value, needs experiments
  14. private const val walkSpeed = 5
  15. private const val bikeSpeed = 10
  16. private var db: SQLiteDatabase? = null
  17. private lateinit var stops: HashMap<String, SimpleStop>
  18. private const val numberOfpeople = 2
  19. private const val wantBikes = true
  20. private const val computingTime = 2000 //in miliseconds
  21. private const val changes = false
  22. private const val bikes = " 16.83362,52.3832,Junikowo\n" +
  23. " 16.80386,52.39727,Malwowa/Złotowska\n" +
  24. " 16.9116229,52.4028313,Poznań Główny\n" +
  25. " 16.9128835,52.4105153,Most Teatralny\n" +
  26. " 16.9252069,52.4083807,Pl. Wolności\n" +
  27. " 16.9378683,52.4104498,Małe Garbary\n" +
  28. " 16.9553804,52.4092652,Rondo Śródka\n" +
  29. " 16.9501609,52.3963767,Rondo Rataje\n" +
  30. " 16.9282258,52.4008945,Półwiejska\n" +
  31. " 16.9523442,52.4122659,Brama Poznania\n" +
  32. " 16.8822312,52.4169808,Ogrody\n" +
  33. " 16.8893659,52.4093307,Bukowska / Przybyszewskiego\n" +
  34. " 16.9038713,52.4124426,Rynek Jeżycki\n" +
  35. " 16.9215095,52.3934764,Rynek Wildecki\n" +
  36. " 16.920619,52.4273709,Słowiańska\n" +
  37. " 16.9475449,52.4557701,Boranta\n" +
  38. " 16.9178295,52.4637887,Os. Sobieskiego\n" +
  39. " 16.902616,52.3979413,Park Wilsona\n" +
  40. " 16.9499141,52.4028182,Politechnika Centrum Wykładowe\n" +
  41. " 16.9087744,52.4210966,Park Sołacki\n" +
  42. " 16.8953902,52.3898524,Głogowska / Hetmańska\n" +
  43. " 16.975615,52.4056982,Termy Maltańskie\n" +
  44. " 16.9029925,52.3721516,Dębiec\n" +
  45. " 16.8765342,52.399447,Grunwaldzka / Grochowska\n" +
  46. " 16.8814105,52.3815165,Górczyn\n" +
  47. " 16.9350171,52.3986614,AWF\n" +
  48. " 16.9435573,52.3807863,Rondo Starołęka\n" +
  49. " 16.9374096,52.4338602,Rondo Solidarności\n" +
  50. " 16.9414008,52.4676583,UAM Pływalnia\n" +
  51. " 16.9433852,52.4481549,Os. Łokietka\n" +
  52. " 16.925388,52.4656288,UAM Wydział Nauk Politycznych i Dziennikarstwa\n" +
  53. " 16.9197235,52.4511427,Kurpińskiego\n" +
  54. " 16.9472694,52.4275148,Wilczak\n" +
  55. " 16.9389224,52.4164704,Garbary\n" +
  56. " 16.9353014,52.4043466,Zielona\n" +
  57. " 16.9785547,52.3999085,Malta SKI\n" +
  58. " 16.9194925,52.4075145,Zamek\n" +
  59. " 16.9773316,52.389895,Os. Lecha\n" +
  60. " 16.9141012,52.3849938,Traugutta\n" +
  61. " 16.8343441,52.4112577,Brzechwy / Maklakiewicza\n" +
  62. " 16.86208,52.396,Bułgarska\n" +
  63. " 16.9393784,52.4077534,Ewangelicka\n" +
  64. " 16.90663,52.41705,Kościelna\n" +
  65. " 16.93543,52.40704,Kozia\n" +
  66. " 16.92216,52.41111,Libelta\n" +
  67. " 16.87527,52.40442,Marcelińska / Grochowska\n" +
  68. " 16.932646,52.409455,Masztalarska\n" +
  69. " 16.89995,52.40338,Matejki\n" +
  70. " 16.97857,52.41499,Nieszawska\n" +
  71. " 16.9248,52.40953,Pl. Cyryla Ratajskiego\n" +
  72. " 16.91564,52.38905,Prądzyńskiego / Kosińskiego\n" +
  73. " 16.9115746,52.4081592,Rondo Kaponiera\n" +
  74. " 16.90098,52.3944,Rynek Łazarski\n" +
  75. " 16.85124,52.40647,Strzegomska / Gałczyńskiego\n" +
  76. " 16.89953,52.41087,Szamarzewskiego / Wawrzyniaka\n" +
  77. " 16.87696,52.41107,Szpitalna\n" +
  78. " 16.9259351,52.4149588,Św. Barbary\n" +
  79. " 16.92028,52.40571,Taczaka\n" +
  80. " 16.916939,52.4060222,Towarowa\n" +
  81. " 16.90508,52.42647,Uniwersytet Przyrodniczy\n" +
  82. " 16.9209,52.4015,Wierzbięcice\n" +
  83. " 16.939228,52.405068,Mostowa\n" +
  84. " 16.92962,52.41025,Aleje Marcinkowskiego\n" +
  85. " 16.9205,52.43632,Aleje Solidarności\n" +
  86. " 16.87798,52.40683,Brzask / Międzychodzka\n" +
  87. " 16.92442,52.42544,Dożynkowa\n" +
  88. " 16.91491,52.40203,Dworzec Autobusowy\n" +
  89. " 16.9097,52.40114,Dworzec Zachodni\n" +
  90. " 16.88617,52.38759,Jarochowskiego / Palacza\n" +
  91. " 16.89209,52.39329,Jarochowskiego / Potworowskiego\n" +
  92. " 16.93598,52.43017,Murawa / Słowiańska\n" +
  93. " 16.90415,52.36353,Os. Dębina\n" +
  94. " 16.9572687,52.3905825,Os. Oświecenia\n" +
  95. " 16.92602,52.42855,Os. Przyjaźni\n" +
  96. " 16.9404781,52.4385695,Os. Wichrowe Wzgórze\n" +
  97. " 16.9317176,52.4048833,Plac Wiosny Ludów\n" +
  98. " 16.91207,52.41376,Poznańska / Jeżycka\n" +
  99. " 16.88974,52.40309,Rondo Jana Nowaka Jeziorańskiego\n" +
  100. " 16.891401,52.412057,Szamarzewskiego / Długosza\n" +
  101. " 16.86674,52.40465,Bułgarska / Marcelińska\n" +
  102. " 16.89584,52.39876,Wyspiańskiego / Ułańska\n" +
  103. " 16.89119,52.38711,Głogowska / ul. Krauthofera\n" +
  104. " 16.93096,52.4388,Połabska\n" +
  105. " 16.97072,52.42123,ul. Główna rejon Rynek Wschodni\n" +
  106. " 16.96008,52.41643,Koronkarska\n" +
  107. " 16.82359,52.40972,Złotowska\n" +
  108. " 16.92183,52.40354,Kościuszki\n" +
  109. " 16.90436,52.456372,os. Jagiełły\n" +
  110. " 16.915363,52.430559,Nasienna\n" +
  111. " 16.94411,52.39038,os. Piastowskie\n" +
  112. " 16.95765,52.38087,os. Stare Żegrze\n" +
  113. " 16.98327,52.38755,Piaśnicka Rynek\n" +
  114. " 16.97061,52.38231,os. Orła Białego\n" +
  115. " 16.98146,52.39383,os. Rusa/Chartowo\n" +
  116. " 16.96276,52.38567,Inflancka\n" +
  117. " 16.86391,52.39078,Keplera\n" +
  118. " 16.95297,52.39908,Kórnicka\n" +
  119. " 16.96224,52.3989,Polanka\n" +
  120. " 16.88343,52.36436,Buczka/Kołłątaja\n" +
  121. " 16.91482,52.42408,Maczka\n" +
  122. " 16.90034,52.3917,Dmowskiego/Stablewskiego\n" +
  123. " 16.9069,52.40652,Targi\n" +
  124. " 16.928468,52.40695,Marcinkowskiego/Podgórna\n" +
  125. " 16.92564,52.39787,Spadzista\n" +
  126. " 16.931681,52.407956,Sieroca\n" +
  127. " 16.946632,52.445027,Karpia\n" +
  128. " 16.94206,52.45386,Jasna Rola"
  129. private class SimpleStop {
  130. var latitude: Float = .0F
  131. var longitude: Float = .0F
  132. var name: String = ""
  133. var bike: Boolean = false
  134. var trips: Boolean = true
  135. var bikes: Int = 0
  136. }
  137. private class Proposition(stop: String, t1: Long, t2: Long, hist: String) {
  138. var name: String = stop
  139. var time: Long = t1
  140. var finish: Long = time + t2
  141. var history: String = hist
  142. }
  143. private fun time(distance: Float, speed: Int): Long {
  144. return (distance * 3600 / speed).toLong()
  145. }
  146. private fun findKClosest(stops: HashMap<String, SimpleStop>, stop: SimpleStop?, k: Int): Array<Pair<Long, SimpleStop>> {
  147. val res: Array<Pair<Long, SimpleStop>> = Array(k + 1) { Pair(Long.MAX_VALUE, SimpleStop()) }
  148. for (s in stops.values) {
  149. res[k] = Pair(time(distance(stop, s), walkSpeed), s)
  150. var i: Int = k - 1
  151. while (i >= 0) {
  152. if (res[i + 1].first < res[i].first) {
  153. val tmp = res[i]
  154. res[i] = res[i + 1]
  155. res[i + 1] = tmp
  156. } else {
  157. break
  158. }
  159. i--
  160. }
  161. }
  162. return res
  163. }
  164. private fun findForBike(stop: SimpleStop?, stops: HashMap<String, SimpleStop>): ArrayList<Pair<Long, SimpleStop>> {
  165. val result: ArrayList<Pair<Long, SimpleStop>> = ArrayList(20)
  166. for (s in stops.values) {
  167. if (!s.bike) continue
  168. var t = time(distance(stop, s), bikeSpeed)
  169. if (t > 15 * 60 * 1000) continue
  170. if (s.trips) t += 30 * 1000
  171. result.add(Pair(t + 60 * 1000, s))
  172. }
  173. return result
  174. }
  175. private fun eucildeanDistance(lat1: Float?, lng1: Float?, lat2: Float?, lng2: Float?): Float {
  176. val earthRadius = 6371000.0 //meters
  177. val dLat = Math.toRadians((lat2!! - lat1!!).toDouble())
  178. val dLng = Math.toRadians((lng2!! - lng1!!).toDouble())
  179. val a = Math.sin(dLat / 2) * Math.sin(dLat / 2) + Math.cos(Math.toRadians(lat1.toDouble())) * Math.cos(Math.toRadians(lat2.toDouble())) *
  180. Math.sin(dLng / 2) * Math.sin(dLng / 2)
  181. val c = 2 * Math.atan2(Math.sqrt(a), Math.sqrt(1 - a))
  182. return (earthRadius * c).toFloat()
  183. }
  184. private fun manhattanDistance(lat1: Float?, lng1: Float?, lat2: Float?, lng2: Float?): Float {
  185. return eucildeanDistance(lat1, lng1, lat2, lng1) + eucildeanDistance(lat2, lng2, lat2, lng1)
  186. }
  187. private fun distance(s0: SimpleStop?, s1: SimpleStop?): Float {
  188. return manhattanDistance(s0!!.latitude, s0.longitude, s1!!.latitude, s1.longitude)
  189. }
  190. private fun distanceToEnd(lat: Float?, lng: Float?, ends: HashSet<String>): Float {
  191. var min = Float.MAX_VALUE
  192. for (stop in ends) {
  193. val dist = manhattanDistance(lat, lng, stops[stop]!!.latitude, stops[stop]!!.longitude)
  194. if (dist < min) min = dist
  195. }
  196. return min
  197. }
  198. private fun getStops(context: Context): HashMap<String, SimpleStop> {
  199. val filesDir = context.getSecondaryExternalFilesDir()
  200. val dbFile = File(filesDir, "timetable.db")
  201. db = try {
  202. SQLiteDatabase.openDatabase(dbFile.path, null, SQLiteDatabase.OPEN_READONLY)
  203. } catch (e: SQLiteException) {
  204. null
  205. }
  206. val cursor = db!!.rawQuery("select stop_lat, stop_lon, stop_name from stops", emptyArray())
  207. val result: HashMap<String, SimpleStop> = HashMap(cursor.count / 3)
  208. while (cursor.moveToNext()) {
  209. val stop = SimpleStop()
  210. stop.latitude = cursor.getFloat(0)
  211. stop.longitude = cursor.getFloat(1)
  212. stop.name = cursor.getString(2)
  213. result[stop.name] = stop
  214. }
  215. cursor.close()
  216. if (wantBikes) {
  217. for (line in bikes.split("\n")) {
  218. val bikeStop = line.split(",")
  219. if (bikeStop[2] in result.keys) {
  220. result[bikeStop[2]]!!.bike = true
  221. result[bikeStop[2]]!!.bikes = 5
  222. } else {
  223. val stop = SimpleStop()
  224. stop.latitude = bikeStop[1].toFloat()
  225. stop.longitude = bikeStop[0].toFloat()
  226. stop.bike = true
  227. stop.bikes = 5
  228. stop.trips = false
  229. stop.name = bikeStop[2]
  230. result[stop.name] = stop
  231. }
  232. }
  233. }
  234. return result
  235. }
  236. private fun pickBest(propositions: HashMap<String, Proposition>): Proposition {
  237. var best = Proposition("", Long.MAX_VALUE, 0, "")
  238. for (p in propositions.values) {
  239. if (p.finish < best.finish) {
  240. best = p
  241. }
  242. }
  243. return best
  244. }
  245. private fun addToPropositions(propositions: HashMap<String, Proposition>, visited: HashMap<String, Proposition>, prop: Proposition) {
  246. if (prop.name in visited.keys) {
  247. if (prop.finish >= visited[prop.name]!!.finish) {
  248. return
  249. }
  250. }
  251. propositions[prop.name] = prop
  252. visited[prop.name] = prop
  253. }
  254. fun findRoute(start: HashSet<String>, end: HashSet<String>, time: JCalendar, context: Context): String {
  255. val startTime: Long = System.currentTimeMillis()
  256. stops = getStops(context)
  257. val visited: HashMap<String, Proposition> = HashMap(500)
  258. timetable = Timetable.getTimetable(context)
  259. val propositions: HashMap<String, Proposition> = HashMap(300)
  260. for (stop in start) {
  261. val startStop = stops[stop]
  262. propositions[stop] = Proposition(stop, time.timeInMillis,
  263. time(distanceToEnd(startStop!!.latitude, startStop.longitude, end), tramSpeed), "")
  264. }
  265. var best = Proposition("", Long.MAX_VALUE, 0, "")
  266. var done = false
  267. while (true) {
  268. val pop = pickBest(propositions)
  269. propositions.remove(pop.name)
  270. if (pop.name in end && pop.time < best.time) {
  271. best = pop
  272. done = true
  273. }
  274. if (done && System.currentTimeMillis() - startTime > computingTime || pop.name == "") {
  275. val x = java.util.Calendar.getInstance()
  276. x.timeInMillis = best.time
  277. System.out.println(x.get(java.util.Calendar.HOUR_OF_DAY).toString() + " " + x.get(java.util.Calendar.MINUTE).toString())
  278. return best.history
  279. }
  280. if (stops[pop.name]!!.trips) {
  281. val x = java.util.Calendar.getInstance()
  282. x.timeInMillis = pop.time
  283. val options = timetable.getTripFrom(pop.name, x)
  284. for (opt in options) {
  285. for (stop in opt.sequence) {
  286. addToPropositions(propositions, visited, Proposition(stop.second.name, stop.first.timeInMillis,
  287. time(distanceToEnd(stop.second.latitude, stop.second.Longitude, end), tramSpeed),
  288. pop.history + " " + pop.name + "-" + opt.route + "-" + stop.second.name))
  289. }
  290. }
  291. }
  292. for (opt in findKClosest(stops, stops[pop.name], 5)) {
  293. addToPropositions(propositions, visited, Proposition(opt.second.name, pop.time + opt.first,
  294. time(distanceToEnd(opt.second.latitude, opt.second.longitude, end), tramSpeed),
  295. pop.history + " " + pop.name + "-" + "walk" + "-" + opt.second.name))
  296. }
  297. for (stop in end) {
  298. addToPropositions(propositions, visited, Proposition(stop,
  299. pop.time + time(distance(stops[pop.name], stops[stop]), walkSpeed), 0,
  300. pop.history + " " + pop.name + "-" + "walk" + "-" + stop)
  301. )
  302. }
  303. if (wantBikes && stops[pop.name]!!.bike && stops[pop.name]!!.bikes > numberOfpeople) {
  304. for (opt in findForBike(stops[pop.name], stops)) {
  305. addToPropositions(propositions, visited, Proposition(opt.second.name, pop.time + opt.first,
  306. time(distanceToEnd(opt.second.latitude, opt.second.longitude, end), tramSpeed),
  307. pop.history + " " + pop.name + "-" + "bike" + "-" + opt.second.name)
  308. )
  309. }
  310. }
  311. }
  312. }
  313. }
  314. }