Current location of the user as a separate point on map

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.

4 Likes