Efficiently ordering line segments into a loop - javascript

I'm using a library (JavaScript-Voronoi) which produces an array of line segments that represent a closed polygon. These segments appear unordered, both the order in which the segments appear as well as the ordering of the points for each end of the segment.
(Edit: As noted in a comment below, I was wrong: the segments from the library are well-ordered. However, the question stands as written: let's assume that the segments do not have any ordering, as this makes it more generally useful.)
For example:
var p1 = {x:13.6,y:13.1}, p2 = {x:37.2,y:35.8}, p3 = {x:99.9,y:14.6},
p4 = {x:99.9,y:45.5}, p5 = {x:33.7,y:66.7};
var segments = [
{ va:p1, vb:p2 },
{ va:p3, vb:p4 },
{ va:p5, vb:p4 },
{ va:p3, vb:p2 },
{ va:p1, vb:p5 } ];
Notice how the first segment links to the last (they share a common point), and to the next-to-last. It is guaranteed that every segment shares an end with exactly one other segment.
I would like to convert this into a list of points to generate a proper SVG polygon:
console.log( orderedPoints(segments) );
// [
// {"x":33.7,"y":66.7},
// {"x":13.6,"y":13.1},
// {"x":37.2,"y":35.8},
// {"x":99.9,"y":14.6},
// {"x":99.9,"y":45.5}
// ]
It doesn't matter whether the points are in clockwise or counter-clockwise order.
The following code is what I've come up with, but in the worst-case scenario it will take n^2+n point comparisons. Is there a more efficient algorithm for joining all these together?
function orderedPoints(segs){
segs = segs.concat(); // make a mutable copy
var seg = segs.pop(), pts = [seg.va], link = seg.vb;
for (var ct=segs.length;ct--;){
for (var i=segs.length;i--;){
if (segs[i].va==link){
seg = segs.splice(i,1)[0]; pts.push(seg.va); link = seg.vb;
break;
}else if (segs[i].vb==link){
seg = segs.splice(i,1)[0]; pts.push(seg.vb); link = seg.va;
break;
}
}
}
return pts;
}

If your polygon is convex, you can pick middle point of each line segment, then use convex hull algorithm to find convex polygon by middle items, after that, because you know what is the arrangement of middles and also you know which middle belongs to which segment, you can find an arrangement in original array.
If you just want to find a convex hull, use convex hull algorithm directly, it's O(n log n), which is fast enough, but also you can find a Quickhull algorithm in javascript here. quickhull is also in O(n logn), but in average, the worst case is O(n^2), but it's fast because of less constant factor.
but in the case of general algorithm:
Set one end of each segment as First, and another end as second (randomly).
Sort your segments by their first x and put it in array First after that in array first sort segments with same first x by their first y and put two extra int into your structure to save start and end position of items with same first x.
Then again sort your segments with the second x value, .... and make array second.
Above actions both are in O(n log n).
Now pick first segment in array First, search for its second x value in both arrays First and second, in the case you find similar values, search for their y values in related subarray (you have start and end position of items with same x). You know there is only one segment with this order (also is not current segment), so finding next segment takes O(log n) and because in all there is n-1 next segment it takes O(n logn) (also preprocessing), which is extremely faster than O(n^2).

It should be possible to turn the points into a (double, unordered?) linked list in linear time:
for (var i=0; i<segments.length; i++) {
var a = segments[i].va,
b = segments[i].vb;
// nexts being the two adjacent points (in unknown order)
if (a.nexts) a.nexts.push(b); else a.nexts = [b];
if (b.nexts) b.nexts.push(a); else b.nexts = [a];
}
Now you can iterate it to build the array:
var prev = segments[0].va,
start = segments[0].vb, // start somewhere, in some direction
points = [],
cur = start;
do {
points.push(cur);
var nexts = cur.nexts,
next = nexts[0] == prev ? nexts[1] : nexts[0];
delete cur.nexts; // un-modify the object
prev = cur;
cur = next;
} while (cur && cur != start)
return points;
If you do not want to modify the objects, an EcmaScript6 Map (with object keys) would come in handy. As a workaround, you could use a JSON serialisation of your point coordinates as keys of a normal object, however you are then limited to polygons that do not contain a coordinate twice. Or just use the unique voronoiId property that your library adds to the vertices for identifying them.

For a convex polygon, you don't even need to know the side segments. You just need a bunch of vertices. The procedure to order the vertices is pretty simple.
average all the vertices together to get a point inside the polygon. note that this doesn't even need to be the centroid. it just needs to be a point inside the polygon. call this point C.
for each vertex V[i], compute the angle the line segment from V[i] to C forms with the line segment from V[i] to V[i]+(1,0). call this a[i].
sort the angles of vertices using the vertices as satellite data.
the sorted vertices are in order around the polygon. there are some redundancies that you can remove. 1 runs in linear time, 2 runs in linear time, 3 runs in n log n.

Related

Stuck in infinite recursive loop (DFS)

I am trying to solve a shortest path algorithm problem using a knight on a chess board. I understand that BFS (breadth first search) is the most efficient approach but I want to try and solve it using DFS (Depth first search) first. Understanding both algorithms will greatly help my knowledge.
Issue/Problem: My nextMove(curRow, curCol, moves) function eventually stack overflows. The base case of my function works because I can see the first stacks return successfully. But as it gets towards the end of possible scenarios it runs infinitely with the same array values.
What I expected: I expect the recursive function to exit when there are no more scenarios left
What I've tried: I tried to pop the array when the recursive function returns so it does not keep trying with the same array values.
Code: https://jsfiddle.net/mschreider/jaqL1c5d/ uncomment line 115 to run
I commented out the console.log() function call because of the stack overflow problem.
function knightMoves(start, finish) {
let shortestPath = []
function nextMove(curRow, curCol, moves) {
//console.log(moves)
if (curRow === finish[0] && curCol === finish[1]) {
if (shortestPath.length === 0) {
shortestPath = moves
}
else {
shortestPath = moves.length < shortestPath.length ? moves : shortestPath
}
console.log(shortestPath)
return
}
// coordinates the knight can move [row, col]
let options = [[1,2], [1,-2], [-1,2], [-1,-2], [2,1], [2,-1], [-2,1], [-2,-1]]
for (let i=0; i<options.length; i++) {
let moveRow = options[i][0]
let moveCol = options[i][1]
let newRow = curRow + moveRow
let newCol = curCol + moveCol
let proceed = validMove(newRow, newCol, moves)
// if there is space to move, move knight
if (proceed) {
let arr = [...moves]
arr.push([newRow, newCol])
nextMove(newRow, newCol, arr)
arr.pop()
}
}
return
}
nextMove(start[0], start[1], [start])
return shortestPath
}
function validMove(row, col, moves) {
// Check if the location has already been seen
let coordinate = [row, col]
let newSpot = true
if (moves.length > 0) {
for (let i = 0; i <moves.length; i++) {
let seen = moves[i].length === coordinate.length && moves[i].every((value, index) => value === coordinate[index])
if (seen) {
return false
}
}
}
else {
newSpot = true
}
// Check if the knight has space to move
if (newSpot) {
if ((row >= 0 && row < 8) && (col >= 0 && col < 8)) {
return true
}
else {
//debugger;
return false
}
}
}
// console.log(knightMoves([0,0],[3,3]))
If it runs forever, the exit condition must be at fault.
So ... what's supposed to exit the recursion? nextMove recurses while validMove returns true. validMove returns false in two cases:
the knight tries to move outside the board. By itself, this doesn't guarantee termination, because the knight can walk in circles.
the knight visits a coordinate he has already visited in this path
The latter ensures that the path length is at most 64 (because otherwise a square would be visited twice), so we are considering at most 8**64 paths. That's up to 6277101735386680763835789423207666416102355444464034512896 paths. Granted, the vast majority of these will leave the board at some point, but if even a tiny fraction remain inside the boundaries, this might take forever.
To investigate whether this really is the issue, let's restrict path lengths, and output the number of paths considered, by inserting the following into validMove:
if (moves.length == maxPathLength) {
visitedPaths++
return false;
}
and running this with various values for maxPathLength:
maxPathLength
visitedPaths
2
16
4
288
10
2509424
12
42251728
13
161594928
14
648090800
15
2359955136
So even just looking a paths of length 15, we are visiting over a billion paths, and the digits keep piling up! There is no way we'll be able to visit every path of length 64 (and such paths exist!) in our lifetime!
So your algorithm is beyond redemption. You need to find a way to exit early, and focus on the solutions you want rather than trying to iterate through the entire solution space. And since you seek the shortest path, processing shorter paths first rather than going down into the rabbit hole is what you need. In other words: breadth first, rather than depth first traversal.
There are two issues here:
Your shortestPath array gets mutated by arr.pop(). When the recursive call that found the target returns, arr is the array reference that is now shared with shortestPath. This means that after the depth-first algorithm would complete, you wouldn't have the answer. This can be fixed by not doing the pop (it doesn't serve a purpose as arr is not used after that) or not taking a copy of the array where you do it now, but push/pop with moves, and take a copy at the moment you assign to shortestPath: that way you ensure shortestPath will never be mutated by a push or pop afterwards.
The depth-first traversal will visit all possible move sequences (that do not revisit a previous square on the path). This number of sequences is too large for any computer to visit in any reasonable time. Wikipedia gives an estimation of 1051 distinct move sequences on an 8x8 board. That's more than there are water molecules in the oceans. In your algorithm the sequences that run via the target square are not visited, but that still amounts to an astronomical number of sequences to visit. You can safely forget about that traversal completing in your lifetime.
Your algorithm is what is called "brute force". For 5x5 it will work in a reasonable time, but it quickly gets out of control for greater boards. Although there are some ways to optimise your algorithm, depth-first is not the right tool for this.
If you want to consider a better alternative for breadth-first, then look into A*, which is an "informed" search, in the category of best-first search algorithms, based on a heuristic. Except for positions that are nearby, a good heuristic in this case is the Euclidean distance between the two positions divided by √3.

Get coordinate point every x kilometers along the polyline

I have an array of coordinates that make up a polyline. Now i would like to get lat/lng point every 5 km from start point to end point on that polyline. Is there some function in Leaflet js for that or some other way of doing this.
Thanks!
As requested, here's an explanation of how to use leaflet geometryutil (GeoUtil from here on out) to do what you want.
First, let's get the total length of your polyline. You can do this using GeoUtil and a reduce function.
const lengths = GeoUtil.accumulatedLengths(latlngs);
const totalLength = lengths.reduce((a, b) => a + b, 0);
Assuming latlngs is an array of L.LatLng objects, GeoUtil.accumulatedLengths will return an array of distances between those latlngs. Then we reduce that array down to the sum of its parts in the reduce statement.
Next, we figure out how many points you're going to have along your line based on the interval you want (5km), and the total distance:
const interval = 5000; // 5km
const totalPoints = Math.floor(totalLength / interval);
So we will have totalPoints points along your line. Now we need to find out how far along the line each point is as a ratio:
const ratios = [];
for (let i = 0; i <= totalPoints; i++) {
const ratio = i / totalPoints;
ratios.push(ratio);
}
So ratios will be an array of numbers between 0 and 1, with the same length as the number of points we expect to have on the line. If this part confuses you, ask for more explanations in a comment.
Now we have that array of ratios, we can use it in GeoUtil.interpolateOnLine:
const points = ratios.map((ratio) =>
GeoUtil.interpolateOnLine(map, latlngs, ratio)
);
points.forEach((point) => {
L.marker(point.latLng).addTo(map);
});
points is an array of L.LatLng points, at equal intervals along your line. Which I believe is what you are looking to achieve.
Working codesandbox
**Note: the use of Math.floor is crucial to get an integer value when calculating how many points along the line you'll have. This may make it feel like we're "leaving off" the last point, but unless your total distance is an exact multiple of your interval distance, this is required to get the math to work.
If you want to set markers on the line you can use the library leaflet-distance-marker.
var line = L.polyline(coords, {
distanceMarkers: {offset: 5000 }
});
Demo
Else take a look into the code and copy it.

Memory-efficient downsampling (charting) of a growing array

A node process of mine receives a sample point every half a second, and I want to update the history chart of all the sample points I receive.
The chart should be an array which contains the downsampled history of all points from 0 to the current point.
In other words, the maximum length of the array should be l. If I received more sample points than l, I want the chart array to be a downsampled-to-l version of the whole history.
To express it with code:
const CHART_LENGTH = 2048
createChart(CHART_LENGTH)
onReceivePoint = function(p) {
// p can be considered a number
const chart = addPointToChart(p)
// chart is an array representing all the samples received, from 0 to now
console.assert(chart.length <= CHART_LENGTH)
}
I already have a working downsampling function with number arrays:
function downsample (arr, density) {
let i, j, p, _i, _len
const downsampled = []
for (i = _i = 0, _len = arr.length; _i < _len; i = ++_i) {
p = arr[i]
j = ~~(i / arr.length * density)
if (downsampled[j] == null) downsampled[j] = 0
downsampled[j] += Math.abs(arr[i] * density / arr.length)
}
return downsampled
}
One trivial way of doing this would obviously be saving all the points I receive into an array, and apply the downsample function whenever the array grows. This would work, but, since this piece of code would run in a server, possibly for months and months in a row, it would eventually make the supporting array grow so much that the process would go out of memory.
The question is: Is there a way to construct the chart array re-using the previous contents of the chart itself, to avoid mantaining a growing data structure? In other words, is there a constant memory complexity solution to this problem?
Please note that the chart must contain the whole history since sample point #0 at any moment, so charting the last n points would not be acceptable.
The only operation that does not distort the data and that can be used several times is aggregation of an integer number of adjacent samples. You probably want 2.
More specifically: If you find that adding a new sample will exceed the array bounds, do the following: Start at the beginning of the array and average two subsequent samples. This will reduce the array size by 2 and you have space to add new samples. Doing so, you should keep track of the current cluster size c(the amount of samples that constitute one entry in the array). You start with one. Every reduction multiplies the cluster size by two.
Now the problem is that you cannot add new samples directly to the array any more because they have a completely different scale. Instead, you should average the next c samples to a new entry. It turns out that it is sufficient to store the number of samples n in the current cluster to do this. So if you add a new sample s, you would do the following.
n++
if n = 1
append s to array
else
//update the average
last array element += (s - last array element) / n
if n = c
n = 0 //start a new cluster
So the memory that you actually need is the following:
the history array with predefined length
the number of elements in the history array
the current cluster size c
the number of elements in the current cluster n
The size of the additional memory does not depend on the total number of samples, hence O(1).

Arrays Inside Objects: Conditional Updating Of One Object's Array From Another Object's Array

DISCLAIMER
I have absolutely no idea how to succinctly describe the nature of the problem I am trying to solve without going deep into context. It took me forever to even think of an appropriate title. For this reason I've found it nearly impossible to find an answer both on here and the web at large that will assist me. It's possible my question can be distilled down into something simple which does already have an answer on here. If this is the case I apologise for the elaborate duplicate
TL;DR
I have two arrays: a main array members and a destination array neighbours (technically many destination arrays but this is the tl;dr). The main array is a property of my custom group object which is auto-populated with custom ball objects. The destination array is a property of my custom ball object. I need to scan each element inside of the members array and calculate distance between that element and every other element in the members group. If there exist other elements within a set distance of the current element then these other elements need to be copied into the current element's destination array. This detection needs to happen in realtime. When two elements become close enough to be neighbours they are added to their respective neighbours array. The moment they become too far apart to be considered neighbours they need to be removed from their respective neighbours array.
CONTEXT
My question is primarily regarding array iteration, comparison and manipulation but to understand my exact dilemma I need to provide some context. My contextual code snippets have been made as brief as possible. I am using the Phaser library for my project, but my question is not Phaser-dependent.
I have made my own object called Ball. The object code is:
Ball = function Ball(x, y, r, id) {
this.position = new Vector(x, y); //pseudocode Phaser replacement
this.size = r;
this.id = id;
this.PERCEPTION = 100;
this.neighbours = []; //the destination array this question is about
}
All of my Ball objects (so far) reside in a group. I have created a BallGroup object to place them in. The relevant BallGroup code is:
BallGroup = function BallGroup(n) { //create n amount of Balls
this.members = []; //the main array I need to iterate over
/*fill the array with n amount of balls upon group creation*/
for (i = 0; i < n; i++) {
/*code for x, y, r, id generation not included for brevity*/
this.members.push(new Ball(_x, _y, _r, _i)
}
}
I can create a group of 4 Ball objects with the following:
group = new BallGroup(4);
This works well and with the Phaser code I haven't included I can click/drag/move each Ball. I also have some Phaser.utils.debug.text(...) code which displays the distance between each Ball in an easy to read 4x4 table (with duplicates of course as distance Ball0->Ball3 is the same as distance Ball3->Ball0). For the text overlay I calculate the distance with a nested for loop:
for (a = 0; a < group.members.length; a++) {
for (b = 0; b < group.members.length; b++) {
distance = Math.floor(Math.sqrt(Math.pow(Math.abs(group.members[a].x - group.members[b].x), 2) + Math.pow(Math.abs(group.members[a].y - group.members[b].y), 2)));
//Phaser text code
}
}
Now to the core of my problem. Each Ball has a range of detection PERCEPTION = 100. I need to iterate over every group.members element and calculate the distance between that element (group.members[a]) and every other element within the group.members array (this calculation I can do). The problem I have is I cannot then copy those elements whose distance to group.members[a] is < PERCEPTION into the group.members[a].neighbours array.
The reason I have my main array (BallGroup.members) inside one object and my destination array inside a different object (Ball.neighbours) is because I need each Ball within a BallGroup to be aware of it's own neighbours without caring for what the neighbours are for every other Ball within the BallGroup. However, I believe that the fact these two arrays (main and destination) are within different objects is why I am having so much difficulty.
But there is a catch. This detection needs to happen in realtime and when two Balls are no longer within the PERCEPTION range they must then be removed from their respective neighbours array.
EXAMPLE
group.members[0] -> no neighbours
group.members[1] -> in range of [2] and [3]
group.members[2] -> in range of [1] only
group.members[3] -> in range of [1] only
//I would then expect group.members[1].neighbours to be an array with two entries,
//and both group.members[2].neighbours and group.members[3].neighbours to each
//have the one entry. group.members[0].neighbours would be empty
I drag group.members[2] and group.members[3] away to a corner by themselves
group.members[0] -> no neighbours
group.members[1] -> no neighbours
group.members[2] -> in range of [3] only
group.members[3] -> in range of [2] only
//I would then expect group.members[2].neighbours and group.members[3].neighbours
//to be arrays with one entry. group.members[1] would change to have zero entries
WHAT I'VE TRIED
I've tried enough things to confuse any person, which is why I'm coming here for help. I first tried complex nested for loops and if/else statements. This resulted in neighbours being infinitely added and started to become too complex for me to keep track of.
I looked into Array.forEach and Array.filter. I couldn't figure out if forEach could be used for what I needed and I got very excited learning about what filter does (return an array of elements that match a condition). When using Array.filter it either gives the Ball object zero neighbours or includes every other Ball as a neighbour regardless of distance (I can't figure out why it does what it does, but it definitely isn't what I need it to do). At the time of writing this question my current code for detecting neighbours is this:
BallGroup = function BallGroup(n) {
this.members = []; //the main array I need to iterate over
//other BallGroup code here
this.step = function step() { //this function will run once per frame
for (a = 0; a < this.members.length; a++) { //members[a] to be current element
for (b = 0; b < this.members.length; b++) { //members[b] to be all other elements
if (a != b) { //make sure the same element isn't being compared against itself
var distance = Math.sqrt(Math.pow(Math.abs(this.members[a].x - this.members[b].x), 2) + Math.pow(Math.abs(this.members[a].y - this.members[b].y), 2));
function getNeighbour(element, index, array) {
if (distance < element.PERCEPTION) {
return true;
}
}
this.members[a].neighbours = this.members.filter(getNeighbour);
}
}
}
}
}
I hope my problem makes sense and is explained well enough. I know exactly what I need to do in the context of my own project, but putting that into words for others to understand who have no idea about my project has been a challenge. I'm learning Javascript as I go and have been doing great so far, but this particular situation has me utterly lost. I'm in too deep, but I don't want to give up - I want to learn!
Many, many, many thanks for those who took the time read my very long post and tried provide some insight.
edit: changed a > to a <
I was learning more on object literals, I'm trying to learn JS to ween myself off of my jQuery dependency. I'm making a simple library and I made a function that adds properties of one object to another object. It's untested, but I think if you were apply something similar it might help. I'll try to find my resources. Btw, I don't have the articles on hand right now, but I recall that using new could incur complications, sorry I can't go any further than that, I'll post more info as I find it.
xObject could be the ball group
Obj2 could be the members
Obj1 could be the destination
/* augment(Obj1, Obj2) | Adds properties of Obj2 to Obj1. */
// xObject has augment() as a method called aug
var xObject = {
aug: augument
}
/* Immediately-Invoked Function Expression (IIFE) */
(function() {
var Obj1 = {},
Obj2 = {
bool: true,
num: 3,
str: "text"
}
xObject.aug(Obj1, Obj2);
}()); // invoke immediately
function augment(Obj1, Obj2) {
var prop;
for (prop in Obj2) {
if (Obj2.hasOwnProperty(prop) && !Obj1[prop]) {
Obj1[prop] = Obj2[prop];
}
}
}

How to determine two polygons is same?

I have a polygon, it has five points like this:
then I add another point to polygon (the red one):
what's the algorithm to determine two polygons is same one (not just angle/length is same, coordinates also same too).
As your same means same shape,size,orientation and position
then it is really simple
you have 2 polygons defined as set of points
A={ a0,a1...a(n-1) } and B={ b0,b1,...b(m-1) }
for starters I assume you have no oversampling (line is always 2 points not more)
compare m,n
if not equal shapes are different so stop
otherwise m==n so I will use just n from now on
find (a(i)==b(j)) where i,j=<0,n)
this is needed in case the polygons are not starting from the same point
otherwise i=0,j=0
for complicated (self intersecting) shapes you need to find unique points
(not duplicates, or the same count of duplicates with the same next point)
otherwise just set i=0 and find j with single O(n) loop
if no common point found stop (not the same polygons)
compare the points
for (k=0;k<n;k++)
{
if (a(i)!=b(j)) return false; // not the same
i++; if (i>=n) i=0;
j++; if (j>=n) j=0;
} return true; // are the same
the point comparison can be done like this if (|a(i)-b(j)|>=max_difference_treshold)
no need to compare sqrt-ed distances the treshold can be powered by 2 instead
I usually use something like 1e-6 or 1e-10 values
For oversampled polygon you need to resample points of booth A,B first
take 3 neighboring points p(i-1),p(i),p(i+1)
compute dx,dy between 2 pairs
d1=p(i)-p(i-1); dx1=p1.x; dy1=p1.y;
d2=p(i+1)-p(i); dx2=p2.x; dy2=p2.y;
if (dx1*dy2==dx1*dy1) then delete p(i) from the set
you should handle the zero cases (any dx,dy is zero) separately prior to this
//import turf library
var turf = require('#turf/turf');
// create polygon using turf or you can directly use geoJSON
var polygon1 = turf.polygon(firstPolygonCoordinates);
var polygon2 = turf.polygon(secondPolygonCoordinates);
// Compare two polygon using turf's booleanEqual method
if (turf.booleanEqual(polygon1, polygon2)) {
// Add your logic here
}
Depending on your point of view. Two rectangles may be the same independently of the position.
If the position is a requirement you will need to compare the vertex
coordinates.
If the position is NOT a requirement, you should compare the
rectangle size. In this case, you need to obtain the distance between
the vertex. In this case, my advice is you should use the rectangle
base and height for comparing.

Categories

Resources