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