3

I'm receiving all distances between a random number of points in a 2 dimensional coordinate system.

How can I visualize this as coordinates on a map in my browser? In case there are many solutions I just want to see the first possible one that my algorithm can come up with.

So here's an extremely easy example:

PointCount = 3  
Distances:  
0-1 = 2  
0-2 = 4  
1-2 = 2  

enter image description here

Does anyone know an easy way (existing solution/framework maybe) to do it using whatever is out there to make it easier to implement?
I was thinking maybe using the html canvas element for drawing, but I don't know how to create an algorithm that could come up with possible coordinates for those points.

The above example is simplified -
Real distance values could look like this:

       (0)  (1)     (2)     (3)
   (0)  0   2344    3333    10000   
   (1)      0       3566    10333   
   (2)              0       12520   
Cold_Class
  • 3,214
  • 4
  • 39
  • 82
  • @DrewNoakes You're right I think, this is not about how I receive the data, it's what I do with it, I can think of a more efficient way myself later, it's more about how to create a map, given the distances between all points – Cold_Class Dec 28 '17 at 20:26
  • 3
    What sort of output are you trying to get? If the three points are colinear like this, then they could be on any straight line you can imagine. If they are not, then it's a fixed triangle that could rotate, reflect, or translate anywhere you like. If there are more than three points, then it's not even a fixed shape. I can't imagine any graph that usefully shows all possibilities. – Scott Sauyet Dec 28 '17 at 20:28
  • @ScottSauyet I'm trying to visualize the input I get to make it more understandable for a human eye. My example is extremely simplified - for real values I receive it will never be possible to put everything on one line – Cold_Class Dec 28 '17 at 20:32
  • thx for everyone's input - it really helped - I rewrote the question a bit to get to the point more – Cold_Class Dec 28 '17 at 20:53
  • @ScottSauyet to Answer your question: I guess the easiest would be a possible set of coordinates for all points – Cold_Class Dec 28 '17 at 20:56
  • @Cold_Class There are either infinitely many such sets or no sets, depending on the data. – Etheryte Dec 28 '17 at 20:57
  • @ScottSauyet yes, I just need one or none - meaning the algorithm can stop once it finds one solution – Cold_Class Dec 28 '17 at 20:59

3 Answers3

2

I'm not sure this is relevant for SO, but anyway...

The way to do this is quite simply to place the points one by one using the data:

  • Pick a random location for the first point (let's say it's 0,0).

  • The second point is on a circle with radius d(0,1) with the first point as its center, so you can pick any point on the circle. Let's pick (d(0,1),0).

  • The third point is at the intersection of a circle with radius d(0,2) and center point 1, and a circle with radius d(1,2) and center point 2. You will get either 0, 1, 2 or an infinity of solutions. If the data comes from real points, 0 shouldn't happen. 1 and infinity are edge cases, but you should still handle them. Pick any of the solutions.

  • The fourth point is at the intersection of 3 circles. Unless you're very unlucky (but you should account for it), there should be only one solution.

  • Continue like this until all points have been placed.

Note that this doesn't mean you'll get the exact locations of the original points: you can have any combination of a translation (the choice of your first point), rotation (the choice of your second point) and symmetry (the choice of your third point) making the difference.

A quick and dirty implementation (not handling quite a few cases, and tested very little):

function distance(p1, p2) {
  return Math.sqrt(Math.pow(p2[0] - p1[0], 2) + Math.pow(p2[1] - p1[1], 2));
}

// adapted from https://stackoverflow.com/a/12221389/3527940
function intersection(x0, y0, r0, x1, y1, r1) {
  var a, dx, dy, d, h, rx, ry;
  var x2, y2;

  /* dx and dy are the vertical and horizontal distances between
   * the circle centers.
   */
  dx = x1 - x0;
  dy = y1 - y0;

  /* Determine the straight-line distance between the centers. */
  d = Math.sqrt((dy * dy) + (dx * dx));

  /* Check for solvability. */
  if (d > (r0 + r1)) {
/* no solution. circles do not intersect. */
return false;
  }
  if (d < Math.abs(r0 - r1)) {
/* no solution. one circle is contained in the other */
return false;
  }

  /* 'point 2' is the point where the line through the circle
   * intersection points crosses the line between the circle
   * centers.  
   */

  /* Determine the distance from point 0 to point 2. */
  a = ((r0 * r0) - (r1 * r1) + (d * d)) / (2.0 * d);

  /* Determine the coordinates of point 2. */
  x2 = x0 + (dx * a / d);
  y2 = y0 + (dy * a / d);

  /* Determine the distance from point 2 to either of the
   * intersection points.
   */
  h = Math.sqrt((r0 * r0) - (a * a));

  /* Now determine the offsets of the intersection points from
   * point 2.
   */
  rx = -dy * (h / d);
  ry = dx * (h / d);

  /* Determine the absolute intersection points. */
  var xi = x2 + rx;
  var xi_prime = x2 - rx;
  var yi = y2 + ry;
  var yi_prime = y2 - ry;

  return [
[xi, yi],
[xi_prime, yi_prime]
  ];
}

function generateData(nbPoints) {
  var i, j, k;
  var originalPoints = [];

  for (i = 0; i < nbPoints; i++) {
originalPoints.push([Math.random() * 20000 - 10000, Math.random() * 20000 - 10000]);
  }
  var data = [];
  var distances;
  for (i = 0; i < nbPoints; i++) {
distances = [];
for (j = 0; j < i; j++) {
  distances.push(distance(originalPoints[i], originalPoints[j]));
}
data.push(distances);
  }
  //console.log("original points", originalPoints);
  //console.log("distance data", data);
  return data;
}

function findPointsForDistances(data, threshold) {
  var points = [];
  var solutions;
  var solutions1, solutions2;
  var point;
  var i, j, k;

  if (!threshold)
threshold = 0.01;

  // First point, arbitrarily set at 0,0
  points.push([0, 0]);

  // Second point, arbitrarily set at d(0,1),0
  points.push([data[1][0], 0]);

  // Third point, intersection of two circles, pick any solution
  solutions = intersection(
points[0][0], points[0][1], data[2][0],
points[1][0], points[1][1], data[2][1]);
  //console.log("possible solutions for point 3", solutions);
  points.push(solutions[0]);

  //console.log("solution for points 1, 2 and 3", points);
  found = true;

  // Subsequent points, intersections of n-1 circles, use first two to find 2 solutions,
  // the 3rd to pick one of the two
  // then use others to check it's valid
  for (i = 3; i < data.length; i++) {
// distances to points 1 and 2 give two circles and two possible solutions
solutions = intersection(
  points[0][0], points[0][1], data[i][0],
  points[1][0], points[1][1], data[i][1]);
//console.log("possible solutions for point " + (i + 1), solutions);
// try to find which solution is compatible with distance to point 3
found = false;
for (j = 0; j < 2; j++) {
  if (Math.abs(distance(solutions[j], points[2]) - data[i][2]) <= threshold) {
    point = solutions[j];
    found = true;
    break;
  }
}
if (!found) {
  console.log("could not find solution for point " + (i + 1));
  console.log("distance data", data);
  console.log("solution for points 1, 2 and 3", points);
  console.log("possible solutions for point " + (i + 1), solutions);
  console.log("distances to point 3",
   distance(solutions[0], points[2]),
    distance(solutions[1], points[2]),
    data[i][2]
    );

  break;
}
// We have found a solution, we need to check it's valid
for (j = 3; j < i; j++) {
  if (Math.abs(distance(point, points[j]) - data[i][j]) > threshold) {
    console.log("Could not verify solution", point, "for point " + (i + 1) + " against distance to point " + (j + 1));
    found = false;
    break;
  }
}
if (!found) {
  console.log("stopping");
  break;
}
points.push(point);
  }
  if (found) {
//console.log("complete solution", points);
return points;
  }
}

console.log(findPointsForDistances([
  [],
  [2344],
  [3333, 3566],
  [10000, 10333, 12520],
]));
console.log(findPointsForDistances([
  [],
  [2],
  [4, 2],
]));
console.log(findPointsForDistances([
  [],
  [4000],
  [5000, 3000],
  [3000, 5000, 4000]
]));
console.log(findPointsForDistances([
  [],
  [2928],
  [4938, 3437],
  [10557, 10726, 13535]
]));

var nbPoints, i;
for (nbPoints = 4; nbPoints < 8; nbPoints++) {
  for (i = 0; i < 10; i++) {
console.log(findPointsForDistances(generateData(nbPoints)));
  }
}

Fiddle here: https://jsfiddle.net/jacquesc/82aqmpnb/15/

jcaron
  • 17,302
  • 6
  • 32
  • 46
  • thanks a lot, this gives me a good idea of how such an algorithm would look like - is there a certain name for this method that I could google to find existing implementations to use? Otherwise I will try to implement such an algorithm myself – Cold_Class Dec 29 '17 at 22:17
  • 1
    Not sure there's a name for that, but it's pretty much straightforward. Quick implementation (though not handling quite a few cases): https://jsfiddle.net/jacquesc/82aqmpnb/7/ – jcaron Dec 29 '17 at 23:09
  • thx a lot for the implementation, once I will get it working I will accept your answer but right now I can't even get it working with simple arrays like: `[ [], [4000], [5000, 3000], [3000, 5000, 4000] ]` – Cold_Class Jan 01 '18 at 22:01
  • Indeed, it was missing a `< 0.01`. Fixed: https://jsfiddle.net/jacquesc/82aqmpnb/8/ – jcaron Jan 01 '18 at 23:13
  • now, it's working with that easy example without decimals, but not with any other more complex data set - this is the actual data I'm getting for example: `[], [2928], [4938, 3437], [10557, 10726, 13535]´ I think it must be because square-roots often have endless decimals, but I don't know how to round all this without causing follow up errors :( – Cold_Class Jan 02 '18 at 19:55
  • 1
    That’s the reason for the `< 0.01`s, you may need to adjust the threshold. On my phone right now, I’ll check later. But in any case I haven’t tested the whole thing much, it’s just meant as a starting point for you... – jcaron Jan 02 '18 at 20:05
  • I changed the threshold around but even on a 100 it can't find a solution - alright I will try to think about how to optimize the code until it will find a solution. Thanks a lot for the starting point already, it helped a lot! :) – Cold_Class Jan 02 '18 at 20:13
  • 1
    There were a few errors in the code (mostly loops not closing in the right places) which I fixed in the code above and the fiddle. I also added code to generate random points and the associated distances, and it seems it all works very well with exact data. I think the data you have is not exact enough, so there are no points actually matching the distances precisely. I have made a few changes that should make it slightly easier to find points anyway in those cases, but it probably requires something a bit more thought to actually get something meaningful. – jcaron Jan 03 '18 at 00:49
  • thx, I understand now how difficult it is to solve this without having/needing exact values - but it did work this time with a threshold of 1000 with my input data set and you improved the fiddle quite a lot it seems, so I gladly mark your answer as accepted :) – Cold_Class Jan 03 '18 at 09:29
1

Minimum working example. Remember that in canvas coordinates, the y value is inverted but you could do something like:

y = canvasHeight - y

If you also have negative points then if would take a little bit of extra work. Also it may be helpful in that case to draw lines and tick marks to visualize the axis.

let canvas = document.getElementById("canvas");
let ctx = canvas.getContext("2d");

let scale = 10;
let radius = 10;

function point(x, y) {
    ctx.fillRect(x*scale, y*scale, radius, radius);
}

// test
point(10, 15);
point(20, 8);
<html>
<body>
<canvas id="canvas" width=1000 height=1000></canvas>
</body>
</html>
Chuck
  • 431
  • 4
  • 10
  • thx, that answers the part of visualising it :) but I still have no idea how to get the coordinates of possible points when just receiving the distances – Cold_Class Dec 28 '17 at 22:08
1

There are plenty of libraries out there.

chartist.js is easy to use and responsive JavaS cript library. I used it last year for basic charts after trying many others but it was the only one that scaling easily in different screen sizes.

chartJS is another better looking library.

And you can use html5 canvas it's easy and fun but it will take time especially in scaling.

To scale and position, you should use the minimum and maximum values for x and y.

Good luck

USER249
  • 1,080
  • 7
  • 14
  • thanks, I did choose chartJS in the end - using the `Scatter Chart` it worked nicely :) – Cold_Class Jan 03 '18 at 09:31
  • I'm glad it was useful. I found this dashboard demo useful, go to the Data Presentation in the menu to see demos for more charting libraries, some of them are interactive showing animations and more information when hovered https://colorlib.com/polygon/gentelella/echarts.html – USER249 Jan 03 '18 at 09:47