Introduction
For my previous post, where I find the signal strength of WiFi spots, I also needed an algorithm to actually locate the spot. I decided to use triangulation, which is commonly used in wireless communications to locate devices based on signal strength from known positions.
As my system uses GPS, I had to consider the Earth’s curvature. Calculating the geographic midpoint on a sphere isn’t trivial, but GeoMidpoint provides a great explanation (Method A).
Method Overview
Convert every geographic coordinate into a 3D Cartesian coordinate. Then apply weights, compute a weighted average, and convert back to geographic coordinates using atan2
and Pythagorean calculations.
Detailed Method
Convert lat/lon to X, Y, Z:
x = cos(lat) * cos(lon) y = cos(lat) * sin(lon) z = sin(lat)
Set the weight: If not supplied, default to 1.
Sum total weight:
wht_total = w1 + w2 + ... + wn
Compute weighted average:
x_avg = (x1*w1 + x2*w2 + ... + xn*wn) / wht_total y_avg = (y1*w1 + y2*w2 + ... + yn*wn) / wht_total z_avg = (z1*w1 + z2*w2 + ... + zn*wn) / wht_total
Convert back to lat/lon:
lon = atan2(y_avg, x_avg) lat = atan2(z_avg, sqrt(x_avg² + y_avg²))
Code (C#)
Coordinate Classes
class GeoCoordinate
{
public double Longitude { get; set; }
public double Latitude { get; set; }
public GeoCoordinate() { Longitude = 0; Latitude = 0; }
public GeoCoordinate(double lat, double lon) { Longitude = lon; Latitude = lat; }
}
class GeoCoordinate_Weighted : GeoCoordinate
{
public double Weight { get; set; } = 1;
public GeoCoordinate_Weighted(double lat, double lon, double weight)
{
Longitude = lon;
Latitude = lat;
Weight = weight;
}
}
class Coordinate3D
{
public double X, Y, Z;
public Coordinate3D() { X = Y = Z = 0; }
public Coordinate3D(double x, double y, double z) { X = x; Y = y; Z = z; }
}
Weighted Midpoint Calculation
static GeoCoordinate CalculateCentre(List<GeoCoordinate_Weighted> input)
{
double totalWeight = 0;
Coordinate3D sum = new Coordinate3D();
foreach (var point in input)
{
double latRad = Rad(point.Latitude);
double lonRad = Rad(point.Longitude);
sum.X += Math.Cos(latRad) * Math.Cos(lonRad) * point.Weight;
sum.Y += Math.Cos(latRad) * Math.Sin(lonRad) * point.Weight;
sum.Z += Math.Sin(latRad) * point.Weight;
totalWeight += point.Weight;
}
sum.X /= totalWeight;
sum.Y /= totalWeight;
sum.Z /= totalWeight;
double lon = Deg(Math.Atan2(sum.Y, sum.X));
double lat = Deg(Math.Atan2(sum.Z, Math.Sqrt(sum.X * sum.X + sum.Y * sum.Y)));
return new GeoCoordinate(lat, lon);
}
static double Deg(double rad) => rad * 180 / Math.PI;
static double Rad(double deg) => deg * Math.PI / 180;
Test Case
Example 1:
var list = new List<GeoCoordinate_Weighted>
{
new GeoCoordinate_Weighted(20, 10, 10),
new GeoCoordinate_Weighted(0, 0, 1),
new GeoCoordinate_Weighted(-20, -10, 1)
};
var centre = CalculateCentre(list);
Console.WriteLine($"Latitude: {centre.Latitude}, Longitude: {centre.Longitude}");
Matches GeoMidPoint output:
- Latitude: 15.26935
- Longitude: 7.48369
Example 2: 10 random global coordinates also match GeoMidPoint’s output.
Conclusion
The weighted midpoint algorithm works as expected. My application accurately matches GeoMidPoint’s output. Great for future triangulation-based location work!