Okay, so after a bit of back and forth with ChatGPT I got a version that works reasonably well. It accepts any number of points and uses the best 3.
This is what we wound up with:
const points = JSON.parse(p1);
return (trilaterate(points));
function trilaterate(points) {
const R = 6371000; // Earth radius in meters
// Convert lat/lon to Cartesian (ECEF)
function toCartesian(lat, lon) {
lat = lat * Math.PI / 180;
lon = lon * Math.PI / 180;
return [
R * Math.cos(lat) * Math.cos(lon),
R * Math.cos(lat) * Math.sin(lon),
R * Math.sin(lat)
];
}
// Convert arc distance to chord distance
function arcToChord(s) {
return 2 * R * Math.sin(s / (2 * R));
}
// Build anchor list
const anchors = points.map(p => ({
xyz: toCartesian(parseFloat(p.lat), parseFloat(p.lon)),
r: arcToChord(parseFloat(p.dist))
}));
if (anchors.length < 3) {
throw new Error("At least 3 reference points are required.");
}
// Initial guess: average of anchors
let x = 0, y = 0, z = 0;
anchors.forEach(a => { x += a.xyz[0]; y += a.xyz[1]; z += a.xyz[2]; });
x /= anchors.length; y /= anchors.length; z /= anchors.length;
// Iteratively refine using Gauss-Newton style updates
for (let iter = 0; iter < 20; iter++) {
let dx = 0, dy = 0, dz = 0;
anchors.forEach(a => {
const dist = Math.sqrt((x - a.xyz[0])**2 + (y - a.xyz[1])**2 + (z - a.xyz[2])**2);
const err = dist - a.r;
if (dist > 0) {
dx += err * (x - a.xyz[0]) / dist;
dy += err * (y - a.xyz[1]) / dist;
dz += err * (z - a.xyz[2]) / dist;
}
});
x -= dx / anchors.length;
y -= dy / anchors.length;
z -= dz / anchors.length;
}
// Project back to the Earth surface
const norm = Math.sqrt(x*x + y*y + z*z);
x = (x / norm) * R;
y = (y / norm) * R;
z = (z / norm) * R;
const lat = Math.asin(z / R) * 180 / Math.PI;
const lon = Math.atan2(y, x) * 180 / Math.PI;
return `${lat}, ${lon}`;
}
I pass a list of locations/distances to the function as a JSON string like so:
[
{
"lat": "47.6145202027",
"lon": "-122.3206316883",
"dist": 12980689.919369303
},
{
"lat": "-31.952087582622525",
"lon": "115.8638444665685",
"dist": 3910344.046993789
},
{
"lat": "59.93253188444967",
"lon": "30.366073129788116",
"dist": 8968163.640954673
},
{
"lat": "3.152073465167341",
"lon": "101.70557490017445",
"dist": 312222.71896776656
},
{
"lat": "3.8307772555426003",
"lon": "103.3476196863522",
"dist": 284803.878753309
}
]
In testing it I found that if I started with just 3 points that were in remote corners of the world the result wuld be quite inaccurate, but as soon as I gave it a single point in my own geographical region the the accuracy improved significantly.