-
Notifications
You must be signed in to change notification settings - Fork 73
/
Copy pathtriangulate.cs
128 lines (104 loc) · 4.97 KB
/
triangulate.cs
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
using System;
using CosineKitty;
//
// triangulate.cs - by Don Cross - 2021-06-22
//
// Example C# program for Astronomy Engine:
// https://github.com/cosinekitty/astronomy
//
namespace triangulate
{
class Program
{
const string UsageText = @"
USAGE: triangulate lat1 lon1 elv1 az1 alt1 lat2 lon2 elv2 az2 alt2
Calculate the best-fit location of a point as observed
from two different locations on or near the Earth's surface.
lat1, lat2 = Geographic latitudes in degrees north of the equator.
lon1, lon2 = Geographic longitudes in degrees east of the prime meridian.
elv1, elv2 = Elevations above sea level in meters.
az1, az2 = Azimuths toward observed object in degrees clockwise from north.
alt1, alt2 = Altitude angles toward observed object in degrees above horizon.
This program extrapolates lines in the given directions from the two
geographic locations and finds the location in space where they
come closest to intersecting. It then prints out the coordinates
of that triangulation point, along with the error radius in meters.
";
static int Main(string[] args)
{
if (args.Length != 10)
{
Console.WriteLine(UsageText);
return 1;
}
// Validate and parse command line arguments.
double lat1 = ParseNumber("lat1", args[0]);
double lon1 = ParseNumber("lon1", args[1]);
double elv1 = ParseNumber("elv1", args[2]);
double az1 = ParseNumber("az1", args[3]);
double alt1 = ParseNumber("alt1", args[4]);
double lat2 = ParseNumber("lat2", args[5]);
double lon2 = ParseNumber("lon2", args[6]);
double elv2 = ParseNumber("elv2", args[7]);
double az2 = ParseNumber("az2", args[8]);
double alt2 = ParseNumber("alt2", args[9]);
var obs1 = new Observer(lat1, lon1, elv1);
var obs2 = new Observer(lat2, lon2, elv2);
// Use an arbitrary but consistent time for the Earth's rotation.
AstroTime time = new AstroTime(0.0);
// Convert geographic coordinates of the observers to vectors.
AstroVector pos1 = Astronomy.ObserverVector(time, obs1, EquatorEpoch.OfDate);
AstroVector pos2 = Astronomy.ObserverVector(time, obs2, EquatorEpoch.OfDate);
// Convert horizontal coordinates into unit direction vectors.
AstroVector dir1 = DirectionVector(time, obs1, alt1, az1);
AstroVector dir2 = DirectionVector(time, obs2, alt2, az2);
// Find the closest point between the skew lines.
return Intersect(pos1, dir1, pos2, dir2);
}
static double ParseNumber(string name, string text)
{
if (double.TryParse(text, out double value) && double.IsFinite(value))
return value;
throw new ArgumentException($"Invalid value for {name}: {text}");
}
static AstroVector DirectionVector(AstroTime time, Observer observer, double altitude, double azimuth)
{
// Convert horizontal angles to a horizontal unit vector.
var hor = new Spherical(altitude, azimuth, 1.0);
AstroVector hvec = Astronomy.VectorFromHorizon(hor, time, Refraction.None);
// Find the rotation matrix that converts horizontal vectors to equatorial vectors.
RotationMatrix rot = Astronomy.Rotation_HOR_EQD(time, observer);
// Rotate the horizontal (HOR) vector to an equator-of-date (EQD) vector.
AstroVector evec = Astronomy.RotateVector(rot, hvec);
return evec;
}
static int Intersect(AstroVector pos1, AstroVector dir1, AstroVector pos2, AstroVector dir2)
{
double F = dir1 * dir2;
AstroVector amb = pos1 - pos2;
double E = dir1 * amb;
double G = dir2 * amb;
double denom = 1.0 - F*F;
if (denom == 0.0)
{
Console.WriteLine("ERROR: Cannot solve because directions are parallel.");
return 1;
}
double u = (F*G - E) / denom;
double v = G + F*u;
if (u < 0.0 || v < 0.0)
{
Console.WriteLine("ERROR: Lines of sight do not converge.");
return 1;
}
AstroVector a = pos1 + u*dir1;
AstroVector b = pos2 + v*dir2;
AstroVector c = (a + b) / 2.0;
AstroVector miss = a - b;
double dist = (Astronomy.KM_PER_AU * 1000 / 2) * miss.Length(); // error radius in meters
Observer obs = Astronomy.VectorObserver(c, EquatorEpoch.OfDate);
Console.WriteLine($"Solution: lat = {obs.latitude:F6}, lon = {obs.longitude:F6}, elv = {obs.height:F3} meters; error = {dist:F3} meters.");
return 0;
}
}
}