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