main.kt 6.7KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219
  1. package name.lamperi.orbital
  2. import java.io.File
  3. import java.util.*
  4. // Latitude = South - North (-90...90)
  5. // Longitude = East West (-180...180)
  6. val EARTH_RADIUS = 6371.0; // KM
  7. data class Problem (
  8. val seed : Double, val satellites : Set<Satellite>, val route : Route)
  9. data class Satellite (
  10. val id : String, val lat : Double, val lon : Double, val height : Double)
  11. data class Route (
  12. val startLat : Double, val startLon : Double, val endLat : Double, val endLon : Double)
  13. data class Point3D (
  14. val x : Double, val y : Double, val z : Double)
  15. data class Vec3 (
  16. val x : Double, val y : Double, val z : Double
  17. ) {
  18. fun len() : Double {
  19. return Math.sqrt(x * x + y * y + z * z)
  20. }
  21. fun unit() : Vec3 {
  22. val l = len()
  23. return Vec3(x/l, y/l, z/l)
  24. }
  25. fun dot(o: Vec3) : Double {
  26. return x * o.x + y * o.y + z * o.z
  27. }
  28. }
  29. fun toVec(point : Point3D) : Vec3 {
  30. return Vec3(point.x, point.y, point.z)
  31. }
  32. fun createVec(start : Point3D, end : Point3D) : Vec3 {
  33. return Vec3(end.x - start.x, end.y - start.y, end.z - start.z)
  34. }
  35. data class SphericalCoordinate (
  36. val radius : Double, val theta : Double, val phi : Double)
  37. data class NamedPoint3D (
  38. val name : String, val point : Point3D)
  39. data class Graph (
  40. val nodes : Map<String,Node>)
  41. data class Node (
  42. val info : NamedPoint3D, val neighbors : Set<String>)
  43. // Coordinate system
  44. // x = + when lon = 0°, - when lon = 180°
  45. // y = + when lon = 90°, - when lon = -90°
  46. // z = + when lat = 90°, - when lat = -90°
  47. fun rad(degrees : Double) = degrees * Math.PI / 180.0
  48. // let theta = rad(lon) in (-pi, pi)
  49. // let phi = rad(-lat + 90°) in (0, pi)
  50. fun toSpherical(lat : Double, lon : Double, radius : Double = EARTH_RADIUS)
  51. = SphericalCoordinate(radius = radius, theta = rad(lon), phi = rad(-lat + 90.0))
  52. // Cartesian coordinates can be calculated using
  53. // x = r * sin(theta) * cos(phi)
  54. // y = r * sin(theta) * sin(phi)
  55. // z = r * cos(theta)
  56. fun toCartesian(c : SphericalCoordinate)
  57. = Point3D(
  58. x = c.radius * Math.sin(c.theta) * Math.cos(c.phi),
  59. y = c.radius * Math.sin(c.theta) * Math.sin(c.phi),
  60. z = c.radius * Math.cos(c.theta))
  61. fun toCartesian(lat : Double, lon : Double)
  62. = toCartesian(toSpherical(lat, lon))
  63. fun toCartesian(lat : Double, lon : Double, height : Double) : Point3D
  64. = toCartesian(toSpherical(lat, lon, EARTH_RADIUS + height))
  65. fun named(name : String, point : Point3D) = NamedPoint3D(name = name, point = point)
  66. fun parseFile(file : java.io.File) : Problem {
  67. val contents : String = file.readText()
  68. val seed : Double = contents.splitToSequence('\n').first().splitToSequence(':').last().toDouble()
  69. val satellites : Set<Satellite> = contents.splitToSequence('\n')
  70. .filter { it.startsWith("SAT") }
  71. .map {
  72. val parts = it.split(',')
  73. Satellite(id = parts[0], lat = parts[1].toDouble(),
  74. lon = parts[2].toDouble(), height = parts[3].toDouble())
  75. }.toSet()
  76. val route : Route = contents.splitToSequence('\n')
  77. .filter { it.startsWith("ROUTE") }
  78. .map {
  79. val parts = it.split(',')
  80. Route(startLat = parts[1].toDouble(), startLon = parts[2].toDouble(),
  81. endLat = parts[3].toDouble(), endLon = parts[4].toDouble())
  82. }.first()
  83. return Problem(seed = seed, satellites = satellites, route = route)
  84. }
  85. // https://en.wikipedia.org/wiki/Line–sphere_intersection
  86. // Solutions are of quadratic form
  87. // l = unit vector from point 0 to point 1
  88. // o = point 0
  89. // c = [0,0,0]
  90. // r = EARTH_RADIUS
  91. // Calculate (l · o)^2 - (o · o) + r^2
  92. // If it is non-zero, then the route cannot be made
  93. fun canRoute(p0 : Point3D, p1 : Point3D) : Boolean {
  94. val l = createVec(p0, p1).unit()
  95. val o = toVec(p0)
  96. val l_dot_o = l.dot(o)
  97. val o_dot_o = o.dot(o)
  98. val result = l_dot_o * l_dot_o - o_dot_o + EARTH_RADIUS * EARTH_RADIUS
  99. if (result <= 0) {
  100. println("$p0 $p1 => $result")
  101. }
  102. return result > 0
  103. }
  104. fun <E> concat(set : Collection<E>, vararg more : E) : Set<E> {
  105. val mutable = set.toMutableSet()
  106. for (e in more) mutable.add(e)
  107. return mutable.toSet()
  108. }
  109. fun buildGraph(problem : Problem) : Graph {
  110. val start = named("Start", toCartesian(problem.route.startLat, problem.route.startLon))
  111. val end = named("End", toCartesian(problem.route.endLat, problem.route.endLon))
  112. val satellites = problem.satellites.map { sat ->
  113. named(sat.id, toCartesian(sat.lat, sat.lon, sat.height))
  114. }.toList()
  115. val startNode = Node(info = start, neighbors = satellites.filter {
  116. canRoute(start.point, it.point)
  117. }.map { it.name }.toSet())
  118. val allPoints = concat(satellites, start, end)
  119. val otherNodes = satellites.map { sat ->
  120. Node(info = sat, neighbors = allPoints.filter { other ->
  121. !sat.name.equals(other.name) && canRoute(sat.point, other.point)
  122. }.map { it.name }.toSet())
  123. }
  124. val endNode = Node(info = end, neighbors = satellites.filter {
  125. canRoute(it.point, end.point)
  126. }.map { it.name }.toSet())
  127. val nodeMap = concat(otherNodes, startNode, endNode).associateBy({ it.info.name }, { it })
  128. return Graph(nodeMap)
  129. }
  130. data class Routing (val node : String, var parent : String?, var distance : Int = Integer.MAX_VALUE)
  131. fun solveGraph(start : String, end : String, graph : Graph) {
  132. val routes : Map<String,Routing> = graph.nodes.map { Routing(node = it.key, parent = null) }.associateBy { it.node }
  133. val queue = LinkedList<String>()
  134. val route = routes[start]!!
  135. route.distance = 0
  136. queue.add(start)
  137. while (!queue.isEmpty()) {
  138. val current = queue.remove()
  139. val currentRoute = routes[current]!!
  140. val route = routes[current]!!
  141. val node = graph.nodes[current]!!
  142. for (neighbor in node.neighbors) {
  143. val neighborRoute = routes[neighbor]!!
  144. if (neighborRoute.distance == Integer.MAX_VALUE) {
  145. neighborRoute.distance = currentRoute.distance + 1
  146. neighborRoute.parent = current
  147. queue.add(neighbor)
  148. }
  149. }
  150. }
  151. val endRoute = routes[end]
  152. if (endRoute != null) {
  153. println("Distance was ${endRoute.distance}")
  154. }
  155. }
  156. fun main(args : Array<String>) {
  157. val file = File("src/main/resources/satellites.txt")
  158. val problem = parseFile(file)
  159. println(problem)
  160. val graph = buildGraph(problem)
  161. println(graph)
  162. solveGraph("Start", "End", graph)
  163. /*
  164. graph.nodes.values.forEach {
  165. println("[\"${it.info.name}\", ${it.info.point.x}, ${it.info.point.y}, ${it.info.point.z}]")
  166. }
  167. */
  168. }