소스 검색

Fixed problems with calculation, splitted files

Toni Fadjukoff 8 년 전
부모
커밋
109a4f88f9

+ 138 - 0
src/main/kotlin/name/lamperi/orbital/geometry.kt 파일 보기

@@ -0,0 +1,138 @@
1
+package name.lamperi.orbital
2
+
3
+
4
+// Latitude = South - North (-90...90)
5
+// Longitude = East West (-180...180)
6
+
7
+val EARTH_RADIUS = 6371.0; // KM
8
+
9
+
10
+data class Point3D (
11
+        val x : Double, val y : Double, val z : Double)
12
+
13
+data class Vec3 (
14
+        val x : Double, val y : Double, val z : Double
15
+) {
16
+    fun len() : Double {
17
+        return Math.sqrt(x * x + y * y + z * z)
18
+    }
19
+
20
+    fun unit() : Vec3 {
21
+        val l = len()
22
+        return Vec3(x/l, y/l, z/l)
23
+    }
24
+
25
+    fun dot(o: Vec3) : Double {
26
+        return x * o.x + y * o.y + z * o.z
27
+    }
28
+
29
+    operator fun plus(o : Vec3) : Vec3 {
30
+        return Vec3(x + o.x, y + o.y, z + o.z)
31
+    }
32
+
33
+    operator fun minus(o : Vec3) : Vec3 {
34
+        return Vec3(x - o.x, y - o.y, z - o.z)
35
+    }
36
+
37
+    operator fun times(d : Double) : Vec3 {
38
+        return Vec3(x * d, y * d, z * d)
39
+    }
40
+}
41
+
42
+fun createVec(start : Point3D, end : Point3D) : Vec3 {
43
+    return Vec3(end.x - start.x, end.y - start.y, end.z - start.z)
44
+}
45
+
46
+data class SphericalCoordinate (
47
+        val radius : Double, val theta : Double, val phi : Double)
48
+
49
+fun toVec(point : Point3D) : Vec3 {
50
+    return Vec3(point.x, point.y, point.z)
51
+}
52
+
53
+// Coordinate system
54
+// x = + when lon = 0°, - when lon = 180°
55
+// y = + when lon = 90°, - when lon = -90°
56
+// z = + when lat = 90°, - when lat = -90°
57
+
58
+fun rad(degrees : Double) = degrees * Math.PI / 180.0
59
+
60
+// let theta = rad(lon) in (-pi, pi)
61
+// let phi = rad(-lat + 90°) in (0, pi)
62
+fun toSpherical(lat : Double, lon : Double, radius : Double = EARTH_RADIUS)
63
+        =  SphericalCoordinate(radius = radius, theta = rad(lat), phi = rad(lon))
64
+
65
+// Cartesian coordinates can be calculated using
66
+// x = r * sin(theta) * cos(phi)
67
+// y = r * sin(theta) * sin(phi)
68
+// z = r * cos(theta)
69
+fun toCartesian(c : SphericalCoordinate)
70
+        = Point3D(
71
+        x = c.radius * Math.cos(c.theta) * Math.cos(c.phi),
72
+        y = c.radius * Math.cos(c.theta) * Math.sin(c.phi),
73
+        z = c.radius * Math.sin(c.theta))
74
+
75
+fun toCartesian(lat : Double, lon : Double)
76
+        = toCartesian(toSpherical(lat, lon))
77
+
78
+fun toCartesian(lat : Double, lon : Double, height : Double) : Point3D
79
+        = toCartesian(toSpherical(lat, lon, EARTH_RADIUS + height))
80
+
81
+
82
+// https://en.wikipedia.org/wiki/Line–sphere_intersection
83
+// Solutions are of quadratic form
84
+// l = unit vector from point 0 to point 1
85
+// o = point 0
86
+// c = [0,0,0]
87
+// r = EARTH_RADIUS
88
+// Calculate (l · o) +- Math.sqrt((l · o)^2 - (o · o)  + r^2)
89
+// If it is non-zero, then the route cannot be made
90
+fun intersectsEarth(p0 : Point3D, p1 : Point3D) : Boolean {
91
+    val dist = createVec(p0, p1).len()
92
+    val l = createVec(p0, p1).unit()
93
+    val o = toVec(p0)
94
+
95
+    val l_dot_o = l.dot(o)
96
+    val o_dot_o = o.dot(o)
97
+    val dis = l_dot_o * l_dot_o - o_dot_o + EARTH_RADIUS * EARTH_RADIUS
98
+    val d1 = - l_dot_o + Math.sqrt(dis)
99
+    val d2 = - l_dot_o - Math.sqrt(dis)
100
+
101
+    val sol1 = o + l * d1
102
+    val sol2 = o + l * d2
103
+
104
+    val sigma = 0
105
+    val sol1intersects = d1 > sigma && dist > d1 - sigma
106
+    val sol2intersects = d2 > sigma && dist > d2 - sigma
107
+
108
+    //println("$p0 $p1 $dist => $d1 $sol1intersects $sol1 and $d2 $sol2intersects $sol2")
109
+
110
+    return sol1intersects || sol2intersects
111
+}
112
+
113
+// SAT0 Point3D(x=3372.4860600513102, y=5850.5619038216755, z=-1254.2621619796448)
114
+// SAT5 Point3D(x=-60.473722764130805, y=408.66870956674063, z=-6972.741992625009)
115
+// Start Point3D(x=-70.20728008240212, y=132.20869520700555, z=-6369.241147792654)
116
+// End Point3D(x=215.17891267006544, y=-3374.1448308119298, z=5399.859784869165))
117
+
118
+fun main(args : Array<String>) {
119
+    val SAT0 = Point3D(x=3372.4860600513102, y=5850.5619038216755, z=-1254.2621619796448)
120
+    val SAT5 = Point3D(x=-60.473722764130805, y=408.66870956674063, z=-6972.741992625009)
121
+    val Start = Point3D(x=-70.20728008240212, y=132.20869520700555, z=-6369.241147792654)
122
+    val End = Point3D(x=215.17891267006544, y=-3374.1448308119298, z=5399.859784869165)
123
+
124
+    println(intersectsEarth(Start, SAT5))
125
+    println(intersectsEarth(SAT5, Start))
126
+
127
+    println(intersectsEarth(End, SAT5))
128
+    println(intersectsEarth(SAT5, End))
129
+
130
+    println(intersectsEarth(Start, SAT0))
131
+    println(intersectsEarth(SAT0, Start))
132
+
133
+    println(intersectsEarth(End, SAT0))
134
+    println(intersectsEarth(SAT0, End))
135
+
136
+    println(intersectsEarth(Start, End))
137
+    println(intersectsEarth(End, Start))
138
+}

+ 90 - 0
src/main/kotlin/name/lamperi/orbital/graph.kt 파일 보기

@@ -0,0 +1,90 @@
1
+package name.lamperi.orbital
2
+
3
+import java.util.LinkedList
4
+
5
+data class Graph (
6
+        val nodes : Map<String, Node>)
7
+
8
+data class Node (
9
+        val info : NamedPoint3D, val neighbors : Set<String>)
10
+
11
+data class NamedPoint3D (
12
+        val name : String, val point : Point3D)
13
+
14
+fun named(name : String, point : Point3D) = NamedPoint3D(name = name, point = point)
15
+
16
+fun canRoute(p0 : Point3D, p1 : Point3D) : Boolean  = !intersectsEarth(p0, p1)
17
+
18
+fun <E> concat(set : Collection<E>, vararg more : E) : Set<E> {
19
+    val mutable = set.toMutableSet()
20
+    for (e in more) mutable.add(e)
21
+    return mutable.toSet()
22
+}
23
+
24
+fun buildGraph(problem : Problem) : Graph {
25
+    val start = named("Start", toCartesian(problem.route.startLat, problem.route.startLon))
26
+    val end = named("End", toCartesian(problem.route.endLat, problem.route.endLon))
27
+
28
+    val satellites = problem.satellites.map { sat ->
29
+        named(sat.id, toCartesian(sat.lat, sat.lon, sat.height))
30
+    }.toList()
31
+
32
+    val startNode = Node(info = start, neighbors = satellites.filter {
33
+        canRoute(start.point, it.point)
34
+    }.map { it.name }.toSet())
35
+
36
+    val allPoints = concat(satellites, start, end)
37
+
38
+    val otherNodes = satellites.map { sat ->
39
+        Node(info = sat, neighbors = allPoints.filter { other ->
40
+            !sat.name.equals(other.name) && canRoute(sat.point, other.point)
41
+        }.map { it.name }.toSet())
42
+    }
43
+
44
+    val endNode = Node(info = end, neighbors = satellites.filter {
45
+        canRoute(it.point, end.point)
46
+    }.map { it.name }.toSet())
47
+
48
+    val nodeMap = concat(otherNodes, startNode, endNode).associateBy({ it.info.name }, { it })
49
+    return Graph(nodeMap)
50
+}
51
+
52
+data class Routing (val node : String, var parent : String?, var distance : Int = Integer.MAX_VALUE)
53
+
54
+fun solveGraph(start : String, end : String, graph : Graph) : List<String> {
55
+    val routes : Map<String,Routing> = graph.nodes.map { Routing(node = it.key, parent = null) }.associateBy { it.node }
56
+    val queue = LinkedList<String>()
57
+    val route = routes[start]!!
58
+    route.distance = 0
59
+    queue.add(start)
60
+    while (!queue.isEmpty()) {
61
+        val current = queue.remove()
62
+        val currentRoute = routes[current]!!
63
+        val route = routes[current]!!
64
+        val node = graph.nodes[current]!!
65
+        for (neighbor in node.neighbors) {
66
+            val neighborRoute = routes[neighbor]!!
67
+            if (neighborRoute.distance == Integer.MAX_VALUE) {
68
+                neighborRoute.distance = currentRoute.distance + 1
69
+                neighborRoute.parent = current
70
+                queue.add(neighbor)
71
+            }
72
+        }
73
+    }
74
+    val endRoute = routes[end]
75
+    val nodes = LinkedList<String>()
76
+    if (endRoute != null) {
77
+        var current = endRoute
78
+        while (current != null) {
79
+            nodes.addFirst(current.node)
80
+            val parent = current.parent
81
+            if (parent != null) {
82
+                current = routes[parent]
83
+            } else {
84
+                current = null
85
+            }
86
+        }
87
+
88
+    }
89
+    return nodes
90
+}

+ 36 - 206
src/main/kotlin/name/lamperi/orbital/main.kt 파일 보기

@@ -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
 }

+ 32 - 0
src/main/kotlin/name/lamperi/orbital/problem.kt 파일 보기

@@ -0,0 +1,32 @@
1
+package name.lamperi.orbital
2
+
3
+import java.io.File
4
+
5
+data class Problem (
6
+        val seed : Double, val satellites : Set<Satellite>, val route : Route)
7
+
8
+data class Satellite (
9
+        val id : String, val lat : Double, val lon : Double, val height : Double)
10
+
11
+data class Route (
12
+        val startLat : Double, val startLon : Double, val endLat : Double, val endLon : Double)
13
+
14
+fun parseFile(file : File) : Problem {
15
+    val contents : String = file.readText()
16
+    val seed : Double = contents.splitToSequence('\n').first().splitToSequence(':').last().toDouble()
17
+    val satellites : Set<Satellite> =  contents.splitToSequence('\n')
18
+            .filter { it.startsWith("SAT") }
19
+            .map {
20
+                val parts = it.split(',')
21
+                Satellite(id = parts[0], lat = parts[1].toDouble(),
22
+                        lon = parts[2].toDouble(), height = parts[3].toDouble())
23
+            }.toSet()
24
+    val route : Route = contents.splitToSequence('\n')
25
+            .filter { it.startsWith("ROUTE") }
26
+            .map {
27
+                val parts = it.split(',')
28
+                Route(startLat = parts[1].toDouble(), startLon = parts[2].toDouble(),
29
+                        endLat = parts[3].toDouble(), endLon = parts[4].toDouble())
30
+            }.first()
31
+    return Problem(seed = seed, satellites = satellites, route = route)
32
+}

+ 22 - 0
src/main/resources/satellites2.txt 파일 보기

@@ -0,0 +1,22 @@
1
+#SEED: 0.8338924294803292
2
+SAT0,29.960781584875704,100.5219106681426,497.4722017828519
3
+SAT1,35.63822561651885,112.43332215054551,456.47908238828165
4
+SAT2,-55.80162550543868,-47.223517349881064,640.9142246937936
5
+SAT3,32.86579634661054,-174.0355355962846,698.19931306801
6
+SAT4,-15.591464895709976,-33.59277356598329,304.7933580025571
7
+SAT5,-8.417394325772605,176.60932116923584,613.9694402366624
8
+SAT6,-31.337812266388397,155.2314281053836,560.0516265972703
9
+SAT7,49.14156578802084,74.11283751074791,526.6089406961383
10
+SAT8,14.574135057446483,10.185507527941525,591.2718668651619
11
+SAT9,-70.25509810245808,98.82497601385126,554.9574524324642
12
+SAT10,-12.262521826261334,-108.02116402606393,511.18350543382644
13
+SAT11,88.60697902520695,-6.794758753832582,465.2422688852638
14
+SAT12,-74.31452614616971,-122.12821653985417,499.0860910406019
15
+SAT13,39.45914461312282,160.23885267889574,338.17382027741985
16
+SAT14,65.35304485452289,-73.56474105609837,593.8472290023869
17
+SAT15,-58.167779487750366,-42.38551559997467,319.37893022472576
18
+SAT16,-9.76622565405492,14.153656079874906,622.198243045025
19
+SAT17,52.69128802012051,16.10254297232882,328.9426940234299
20
+SAT18,-29.893807409233766,-4.61504227361857,466.55133207065796
21
+SAT19,-13.659850262548488,-111.17258206114808,314.37740871224423
22
+ROUTE,-27.969810777069718,178.65364839117876,-3.648975104018305,-32.051802501382355

+ 22 - 0
src/main/resources/satellites3.txt 파일 보기

@@ -0,0 +1,22 @@
1
+#SEED: 0.2870806611608714
2
+SAT0,-72.88739122139259,88.37189957420514,357.72708394243614
3
+SAT1,-61.551791451352095,-5.202493771452197,550.3065489323669
4
+SAT2,-40.47561565887025,-96.34566789975146,356.26443437386456
5
+SAT3,-29.236492259448127,131.4125359961049,440.25116142954755
6
+SAT4,-73.41486832164533,-72.82799390357344,407.34756551842503
7
+SAT5,53.87023393776735,-19.72508238842161,579.4188734782899
8
+SAT6,-28.111501808932736,44.41336256964237,678.9061048441274
9
+SAT7,41.85417111315573,-162.74550858447142,621.3134570736974
10
+SAT8,22.63435513250704,-45.81501413773705,423.72436693061235
11
+SAT9,-4.497114575798491,30.626326604533205,580.6365972545068
12
+SAT10,42.70955120863053,-78.4354238981028,410.31511446513844
13
+SAT11,39.389485293832905,114.16219122746367,424.9717160822081
14
+SAT12,-14.922870540031496,-129.6140791328477,528.1994774237129
15
+SAT13,50.28262493639784,-148.66305153149187,667.9730765836259
16
+SAT14,15.451225630694111,-111.3528503196552,668.3063334222632
17
+SAT15,-4.6531482250959755,71.88045498382726,338.9641279066445
18
+SAT16,41.111820036412496,96.70488116609306,647.4434963429919
19
+SAT17,-67.42281105897038,56.77472423719638,644.359680146807
20
+SAT18,-82.92341653749764,38.62374824248681,501.21586915106195
21
+SAT19,-3.111895215084914,-65.58327679758636,378.5792354737234
22
+ROUTE,5.714156454649,-111.94544651946063,-84.94681944535331,137.24150287707027