Implementing the Bentley-Ottmann algorithm - c#

I am having some trouble correctly implementing the Bentley-Ottmann algorithm in C#. I am trying to implement it according to the pseudocode here. I have posted my main code below. Assuming my BST and PriorityQueue classes are implemented correctly, do you see any problems with the code?
There are no errors, but not all intersection points are found, only some. My guess is that there's an error in the else part of the code (when the current event is an intersection point). I'm not sure what the pseudocode means by swapping the position of two segments in the BST. Is the way I do it fine? Because in the end, the two aren't really swapped in the BST. I can't just change their positions either, because that could break the BST properties.
Also, am I right in assuming that segments are ordered in the BST by the Y-coordinate of their left endpoint?
Another error I've noticed that I can't seem to be able to track down is that sometimes the point (0, 0) gets into eventList. (0, 0) is outputted by Geometry.Intersects in case there is no intersection, but in that case the if conditions should stop it from getting added. I have no idea how it gets in. If I print the contents of eventList after adding a point in, (0, 0) never shows up. If I print the contents after extracting and popping an element, (0, 0) sometimes shows up. Could this have anything to do with the Pop() method messing with the references, or is it definitely a problem in my PriorityQueue implementation?
If needed I can show my implementations for the BST and priority queue as well.
static class BentleyOttman
{
private static void AddIntersectionEvent(PriorityQueue eventList, Segment segEv, Segment segA, SegPoint i)
{
i.IntersectingSegments = new Tuple<Segment, Segment>(segEv, segA);
i.Type = SegmentPointType.IntersectionPoint;
eventList.Add(i);
}
public static void Solve(Panel surface, TextBox debug)
{
debug.Clear();
var segList = Generator.SegList;
PriorityQueue eventList = new PriorityQueue();
foreach (Segment s in segList)
{
eventList.Add(new SegPoint(s.A, s, SegmentPointType.LeftEndpoint));
eventList.Add(new SegPoint(s.B, s, SegmentPointType.RightEndpoint));
}
BST sweepLine = new BST();
while (!eventList.Empty)
{
SegPoint ev = eventList.Top();
eventList.Pop();
if (ev.Type == SegmentPointType.LeftEndpoint)
{
Segment segEv = ev.Segment;
sweepLine.Insert(segEv);
Segment segA = sweepLine.InorderPre(segEv);
Segment segB = sweepLine.InorderSuc(segEv);
SegPoint i = new SegPoint();
if (segA != null && Geometry.Intersects(segEv, segA, out i.Point))
{
AddIntersectionEvent(eventList, segA, segEv, i);
}
if (segB != null && Geometry.Intersects(segEv, segB, out i.Point))
{
AddIntersectionEvent(eventList, segEv, segB, i);
}
}
else if (ev.Type == SegmentPointType.RightEndpoint)
{
Segment segEv = ev.Segment;
Segment segA = sweepLine.InorderPre(segEv);
Segment segB = sweepLine.InorderSuc(segEv);
sweepLine.Remove(segEv);
SegPoint i = new SegPoint();
if (segA != null && segB != null && Geometry.Intersects(segA, segB, out i.Point))
{
AddIntersectionEvent(eventList, segA, segB, i);
}
}
else
{
Generator.DrawPoint(ev.Point, surface, Brushes.Red);
Segment seg1 = ev.IntersectingSegments.Item1;
Segment seg2 = ev.IntersectingSegments.Item2;
sweepLine.Remove(seg1);
sweepLine.Remove(seg2);
Segment t = new Segment(seg1);
seg1 = new Segment(seg2);
seg2 = new Segment(t);
sweepLine.Insert(seg1);
sweepLine.Insert(seg2);
Segment segA = sweepLine.InorderPre(seg2);
Segment segB = sweepLine.InorderSuc(seg1);
SegPoint i = new SegPoint();
if (segA != null && Geometry.Intersects(seg2, segA, out i.Point))
AddIntersectionEvent(eventList, segA, seg2, i);
if (segB != null && Geometry.Intersects(seg1, segB, out i.Point))
AddIntersectionEvent(eventList, seg1, segB, i);
}
}
}
}

I really cannot understand your code without some idea of what exactly the other classes do, but I can answer some of your other questions.
The segments are ordered in the BST by the Y coordinate of their intersection with the sweep line. So when we encounter a left endpoint we add the segment to the tree using the y coordinate of the left endpoint of the entering segment (comparing it with the Y coordinate of the intersection of the other segment with the sweep line). When we encounter a right endpoint we remove the segment from the tree. When we encounter an intersection, then the order of the intersections of the two segments with the sweep line switches, so we swap the two segments in the tree. For example consider the two segments
A = {(-1,1),(1,-1)} and
B = {(-1,-1),(1,1)}
When the X coordinate of the sweep line is less than 0 then the intersection of segment A with the sweep line is greater than the intersection of segment B with the sweep line. and if the sweep line is greater than 0 the reverse is true. (Draw a picture.)
It is probably instructive to draw a simple example, and trace what is going on step by step, drawing the sweep line for each event and labeling the segments in columns between the events, then keeping track of the BST and verifying that the BST keeps the same order as the segments in the region where it is valid. (I'm sorry if that is not as clear as it could be.)
Note: This assumes that your segments are in "general position", i.e. that no segment is vertical, no more than two segments intersect at a given point, etc. Dealing with segments not in general position is outlined on the wikipedia page

Related

Check if lines forming a loop

Suppose I have a simple class with properties of a vertical line on a 2D Cartesian coordinate plane such as this:
public class VerticalLine
{
int x, y1, y2;
...
}
Assume that Y1 can be smaller or larger than Y2, but Y1 and Y2 never contain the same value.
In runtime, I will gather a collection(List) of vertical lines in similar format:
List<VerticalLine> lines = new List<VerticalLine>
{
new VerticalLine() { X=12, Y1=3, Y2=15 },
new VerticalLine() { X=23, Y1=23, Y2=5 },
new VerticalLine() { X=32, Y1=12, Y2=10 },
new VerticalLine() { X=37, Y1=15, Y2=12 },
...
};
Note that there are no specific order of the lines in the list.
By drawing imaginary horizontal lines from one end of vertical line to an end of another line, I need to check that if the lines in the above list will be able to connect and form a loop. In other words, pick a line in list, connect one end to an end of another line using a horizontal line, and then to next line and so on... until the last line connect back to the original line. See image for example.
Note that:
ALL the lines in the list must be used.
The order of the lines list is not important in finding the loop. Any line can be the first line.
The directions of lines are not important in finding the loop, meaning Y2 of one line may try to connect to Y1 or Y2 of the next line, as long as they share the same Y value.
In simple words, my problem is how to write a method to check if lines can form a loop.
My first thought of doing so will be using a recursive method to exhaustively try connecting the next line. But this way will involve a large amount of list manipulations, which can be slow. I wonder if there are 'better' way to achieve the same goal.
This is a working solution I have so far. It is simple but probably not the most efficient way of doing so. It is just a recursive and exhaustive method.
public bool CheckForLoop (List<VerticalLine> lines)
{
if (lines == null || lines.Count < 2)
return false;
VerticalLine head = lines[0];
return CheckForLoop(head.Y1, head.Y2, lines.GetRange(1, lines.Count - 1));
}
// helper method for recursion
private bool CheckForLoop(int start, int last, List<VerticalLine> remaining)
{
if (remaining.Count == 1)
{
return remaining[0].Y1 == start && remaining[0].Y2 == last || remaining[0].Y2 == start && remaining[0].Y1 == last;
}
foreach (var next in remaining)
{
bool foundMatching = false;
int newLast = 0;
if (next.Y1 == last)
{
foundMatching = true;
newLast = next.Y2;
} else if (next.Y2 == last)
{
foundMatching = true;
newLast = next.Y1;
}
if (foundMatching)
{
List<VerticalLine> remaining2 = new List<VerticalLine>(remaining);
remaining2.Remove(next);
if (CheckForLoop(start, newLast, remaining2))
{
return true;
}
}
}
return false;
}
What I don't like about this is that it requires a separate helper method for the recursion. I would be best if everything is done within the one method itself.

C# XNA A* Pathfinding Enemies Stuck Opposite Walls

I have a 2D grid in my game made up of nodes. I have enemies which follow players using the A* pathfinding algorithm (using the diagonal distance heuristic for H as diagonal movement is allowed).
The pathfinding works nearly all of the time however, when a player and an enemy are on exactly opposite (diagonally, vertically, or horizontally) sides of a wall, the enemy becomes stuck and stops moving.
From the below screenshot you can see the path found in this scenario, for some reason, a node in the opposite direction of the path is also being added to the path:
Below is my code for my F, G and H calculations (in my node class):
// Calculates distance cost from current node to an adjacent node.
public void CalculateG(Node nodeTarget)
{
// If the node is diagonal to the current node, its cost of movement is higher.
if (TileX != nodeTarget.TileX && TileY != nodeTarget.TileY)
{
intG = 14;
}
else intG = 10;
}
// Calculates H cost using the diagonal shortcut method.
public void CalculateH(Node nodeTarget)
{
int intXDiff = Math.Abs(TileX - nodeTarget.TileX);
int intYDiff = Math.Abs(TileY - nodeTarget.TileY);
if (intXDiff > intYDiff)
intH = 14 * intYDiff + 10 * (intXDiff - intYDiff);
else intH = 14 * intXDiff + 10 * (intYDiff - intXDiff);
}
public void CalculateF()
{
intF = intG + intH; // F = G + H
}
The code for my pathfinding class is shown below:
public class Path
{
private List<Node> PathOfNodes; // Stores the path of nodes to follow to the destination.
public List<Node> NodePath
{
get { return PathOfNodes; }
}
// Constructor takes the starting node, destination and the grid to generate the path.
public Path(Node Start, Node Destination, GridLayer grid)
{
List<Node> openNodes = new List<Node>(); // Creates a list of possible nodes for the path.
List<Node> closedNodes = new List<Node>(); // Creates a list of nodes confirmed for the path.
openNodes.Add(Start); // Step 1: Adds the current node to the possibilities list.
// Loops while the destination is not on the closed list and while the open nodes list is not empty.
while (!closedNodes.Contains(Destination) && openNodes.Any())
{
// Sorts the open list according to f scores.
openNodes.Sort((node, otherNode) => node.F.CompareTo(otherNode.F));
Node nodeCurrent = openNodes[0]; // The node with the lowest F score is set as the current node.
openNodes.Remove(nodeCurrent);
closedNodes.Add(nodeCurrent); // The current node is moved to the closed list.
// Creates a list containing all the nodes adjacent to the current node.
List<Node> adjacentNodes = AddAdjacentNodes(grid, nodeCurrent);
CheckAdjacentNodes(adjacentNodes, ref closedNodes, ref openNodes, nodeCurrent, Destination);
}
EstablishPath(closedNodes);
}
// Adds all the adjacent nodes from above the current node turning clockwise.
public List<Node> AddAdjacentNodes(GridLayer grid, Node nodeCurrent)
{
int intCol = nodeCurrent.TileX / nodeCurrent.TileWidth;
int intRow = nodeCurrent.TileY / nodeCurrent.TileHeight; // Gets the current node's indices.
List<Node> adjacentNodes = new List<Node>(); // Stores the nodes adjacent to the current node.
// The if statements check whether the node is within the grid before adding the node.
if (intRow - 1 >= 0)
adjacentNodes.Add(grid.Nodes[intCol, intRow - 1]); // Above
if ((intCol + 1 < 21 && intRow - 1 >= 0) && (grid.Nodes[intCol + 1, intRow].Traversable) && (grid.Nodes[intCol, intRow - 1].Traversable))
adjacentNodes.Add(grid.Nodes[intCol + 1, intRow - 1]); // Diagonally Right Up
if (intCol + 1 < 21)
adjacentNodes.Add(grid.Nodes[intCol + 1, intRow]); // Right
if (intCol + 1 < 21 && intRow + 1 < 12 && (grid.Nodes[intCol + 1, intRow].Traversable) && (grid.Nodes[intCol, intRow + 1].Traversable))
adjacentNodes.Add(grid.Nodes[intCol + 1, intRow + 1]); // Diagonally Right Down
if (intRow + 1 < 12)
adjacentNodes.Add(grid.Nodes[intCol, intRow + 1]); // Below
if (intCol - 1 >= 0 && intRow + 1 < 12 && (grid.Nodes[intCol - 1, intRow].Traversable) && (grid.Nodes[intCol, intRow + 1].Traversable))
adjacentNodes.Add(grid.Nodes[intCol - 1, intRow + 1]); // Diagonally Left Down
if (intCol - 1 >= 0)
adjacentNodes.Add(grid.Nodes[intCol - 1, intRow]); // Left
if (intCol - 1 >= 0 && intRow - 1 >= 0 && (grid.Nodes[intCol - 1, intRow].Traversable) && (grid.Nodes[intCol, intRow - 1].Traversable))
adjacentNodes.Add(grid.Nodes[intCol - 1, intRow - 1]); // Diagonally Left Up
return adjacentNodes;
}
// Checks the adjacent node list for nodes to be added to the open list/closed list.
private void CheckAdjacentNodes(List<Node> adjacentNodes, ref List<Node> closedNodes, ref List<Node> openNodes, Node nodeCurrent, Node destinationNode)
{
foreach (Node node in adjacentNodes)
{ // Checks each node to see if it is traversable and not already on the closed list.
if (node.Traversable && !closedNodes.Contains(node))
{
// If the node is not on the open list, add it, set its parent as the current node and calculate its F, G, and H values.
if (!openNodes.Contains(node))
{
openNodes.Add(node);
node.Parent = nodeCurrent;
node.CalculateG(nodeCurrent);
node.CalculateH(destinationNode);
node.CalculateF();
}
else // If the node was already on the open list...
{
// If its G cost of the node is lower than its parent + its own...
if (node.G < node.G + node.Parent.G)
{
// Make the node's parent the current node and recalculate its values.
node.Parent = nodeCurrent;
node.CalculateG(nodeCurrent.Parent);
node.CalculateF();
}
}
}
}
}
private void EstablishPath(List<Node> closedNodes)
{
PathOfNodes = new List<Node>(); // Stores the path for the entity to follow to its target.
for (int intNodeIndex = closedNodes.Count - 1; (intNodeIndex > 1); intNodeIndex--)
{
// Starting from the top of the closed list, add each node's parent to the path
// until the starting node is reached (index is 0).
PathOfNodes.Add(closedNodes[intNodeIndex].Parent);
}
PathOfNodes.Remove(null); // Remove the final null node (as the final node has no parent).
}
}
I believe there is a problem with the comparision between a node's G score and the G score of itself and its parent. I'm not sure if I am recalculating the G score using the correct adjacent node after the comparsion. If someone could make this clearer it would be really helpful. The article I followed was:
https://www.gamedev.net/resources/_/technical/artificial-intelligence/a-pathfinding-for-beginners-r2003
however, I don't think I have implemented this step into my code correctly:
If it is on the open list already, check to see if this path to that square is better, using G cost as the measure. A lower G cost means that this is a better path. If so, change the parent of the square to the current square, and recalculate the G and F scores of the square.
Please let me know of any additional information needed for this problem.
Thanks in advance.
Edit: I don't have any collision detection set up for enemies colliding with walls. The path is followed by the enemy moving towards the last node in the node path list.
Edit: My G calculation is wrong, the scores are not accumulated.
After correctly accumulating the G scores, it's now finding paths in all 4 directions (with the heuristic set to 0). I'm guessing this means that all the closed list nodes are being added to my final path meaning a problem in my establish path method.
The red numbers show the f score of the node, the blue numbers show the f score of the parent of the node (so you can tell which node is its parent).
After correctly accumulating the G scores, it's now finding paths in all 4 directions (with the heuristic set to 0).
Edit: I've fixed the problem. I'll submit an answer shortly but basically, my establish path method didn't do what it should. Instead of adding everything from the closed list, it was supposed to start at the end of the closed list follow through the chain of parents from the end until the starting node was added.
I do see some flaws in your code. This might not be the problem, but let's hope it is.
When you're checking if the node is better than other node in the openlist, you're doing something weird. That if-statement never even triggers, because something is never smaller than itself + a positive integer. Also you didn't even set the G-cost of the node yet. So therefore you can't check it either. This piece of code probably would result in a error (unless G has a standard value).
However I think this piece of code doesn't even get reached. Because I suspect that this piece of code allways triggers:
if (!openNodes.Contains(node))
You know why?
I can see the idea your trying to achieve, but there isn't another exact copy of the node in the openset. First of all because the nodes in the openset have their G, H and F cost set, and the node you're checking doesn't (so they're not the same). Thereby if the had the same data in them, I think it still wouldn't trigger because both nodes have another location in your computer (not 100% sure about this part). This also goes for checking if the node is in the closedset.
What you should be doing is check if there is a node in the openlist with the same location as the node you're checking. If the node is already in the list, than you should calculate the G-cost of the node we're dealing with. And check if the G-cost is smaller than the G-cost currently in the list, and change parent etc accordingly.
For the rest your code seems fine, although it's often hard to see mistakes in pathfinder. Hope this help, if you have any questions don't hesitate to ask.
First of all, thanks to J4stM4rt for identifying that I was making a useless comparison (integerA < integerA + positive integer) and Eric Lippert for suggesting that I should set the heuristic to 0 to identify the problem.
My first problem was that I was not accumulating the g-scores for the path. In my G score calculation method I needed to have intG = 10 (or 14) + targetNode.G.
The second problem was the problem mentioned by J4stM4rt in that I was checking if the node's g score was greater than itself + another node's g score which would always be true.
The final problem was in the establish path method, I was adding the parent of every node in the closed list to the path instead of starting at the end and adding each subsequent parent to the path until I reached the start. Below you can see the corrections to my code:
G-Score calculation:
// Calculates distance cost from start node to an adjacent node.
public void CalculateG(Node nodeTarget)
{ // The g-score is cumulative so the target node's score is also added.
// If the node is diagonal to the current node, its cost of movement is higher.
if (TileX != nodeTarget.TileX && TileY != nodeTarget.TileY)
{
intG = nodeTarget.G + 14;
}
else intG = nodeTarget.G + 10;
}
Establish path method:
private void EstablishPath(List<Node> closedNodes, Node nodeStart, Node nodeDestination)
{
PathOfNodes = new List<Node>(); // Stores the path for the entity to follow to its target.
// Start at the end of the path.
Node nodeToAdd = closedNodes[closedNodes.Count - 1];
while (!PathOfNodes.Contains(nodeStart))
{ // Add each node's parent until the start is reached.
PathOfNodes.Add(nodeToAdd);
nodeToAdd = nodeToAdd.Parent;
}
PathOfNodes.Remove(null); // Remove the final null node (as the final node had no parent).
}
G score comparison (this may still actually be wrong, I tried changing it to (node.G (cost of moving from the current node's parent to this node) < nodeCurrent.G (cost of moving from the current nodes parent to the current node) + calculation of g for moving from current node to this node) however, this resulted in my enemies becoming stuck again so I changed it to the following and it works seemingly ok:
if (node.G < nodeCurrent.G)
{
// Make the node's parent the current node's parent and recalculate its values.
node.Parent = nodeCurrent;
node.CalculateG(nodeCurrent);
node.CalculateF();
}

How to iterate through a list so that the last iteration step goes back to the first object?

I have a list of points that form a plane. I want to create edges between consecutive points and add them to another list.
Here is the code I currently have:
// Get points forming the plate
ArrayList points = part.points;
// Number of points forming the plate
int pointCount = points.Count;
// Create edges
List<LineSegment> edges = new List<LineSegment>();
for (int i = 0; i < pointCount - 1; i++)
{
// Get start and end points
Point start = points[i];
Point end = points[i+1];
// Create edge
LineSegment edge = new LineSegment(start, end);
// Add edge to the list
edges.Add(edge);
}
It doesn't quite work because it doesn't create the last edge between the last and the first points on the list. What would be the way to correct it? I could make it work with an if statement like this:
for (int i = 0; i < pointCount; i++)
{
// Get start and end points
Point start = points[i] as Point;
Point end;
if (i == pointCount-1) end = points[0] as Point;
else end = points[i+1] as Point;
// Rest of the code here
}
But I'm sure there is a more elegant way to do it. In Python I would start the loop from -1 so that the first edge would actually be connecting the last point to the first point, but that is not possible in C#.
EDIT: The points list is given as an ArrayList by the API.
An 'elegant' solution is using a modulo:
for (int i = 0; i < pointCount; i++)
{
…
// for i+1 == pointCount this will yield points[0]
Point end = points[(i+1) % pointCount] as Point;
…
}
However, I believe the if statement you used is more readable.
Note: Use a List<T> instead of ArrayList, too.
Use the modulo operator (which is ´%´ in C#).
for (int i = 0; i < pointCount; i++)
{
// Get start and end points
Point start = points[i] as Point;
Point end = points[(i + 1) % pointCount];
// Rest of the code here
}
I would kill all off all the indices and the explicit loops and describe the problem itself, rather than how to solve it.
var offsetPoints = points.Skip(1).Concat(new[]{points.First()});
List<LineSegment> edges =
points.Zip(offsetPoints, (p1, p2) => new LineSegment(p1, p2)).ToList();
I just create an offset list that skips the first element at the start and adds it at the end so the offset list and the original list are the same length.
For illustration, I'd be starting with some sequence:
{p1, p2, p3, p4, p5}
And producing a sequence:
{p2, p3, p4, p5, p1}
Then I zip them together, creating a line segment from the points that appear at the same position in each sequence.
Continuing with the above example, creating a new sequence from the elements in both using an arbitrary function:
{f(p1, p2), f(p2, p3), f(p3, p4), f(p4, p5), f(p5, p1)}
where f is my supplied zipping function.
This approach requires far less code and is far less likely to lead to indexing bugs.

Segmenting GPS path data

My Problem
I have a data stream coming from a program that connects to a GPS device and an inclinometer (they are actually both stand alone devices, not a cellphone) and logs the data while the user drives around in a car. The essential data that I receive are:
Latitude/Longitude - from GPS, with a resolution of about +-5 feet,
Vehicle land-speed - from GPS, in knots, which I convert to MPH
Sequential record index - from the database, it's an auto-incrementing integer and nothing ever gets deleted,
some other stuff that isn't pertinent to my current problem.
This data gets stored in a database and read back from the database into an array. From start to finish, the recording order is properly maintained, so even though the timestamp that is recorded from the GPS device is only to 1 second precision and we sample at 5hz, the absolute value of the time is of no interest and the insertion order suffices.
In order to aid in analyzing the data, a user performs a very basic data input task of selecting the "start" and "end" of curves on the road from the collected path data. I get a map image from Google and I draw the curve data on top of it. The user zooms into a curve of interest, based on their own knowledge of the area, and clicks two points on the map. Google is actually very nice and reports where the user clicked in Latitude/Longitude rather than me having to try to backtrack it from pixel values, so the issue of where the user clicked in relation to the data is covered.
The zooming in on the curve clips the data: I only retrieve data that falls in the Lat/Lng window defined by the zoom level. Most of the time, I'm dealing with fewer than 300 data points, when a single driving session could result in over 100k data points.
I need to find the subsegment of the curve data that falls between those to click points.
What I've Tried
Originally, I took the two points that are closest to each click point and the curve was anything that fell between them. That worked until we started letting the drivers make multiple passes over the road. Typically, a driver will make 2 back-and-forth runs over an interesting piece of road, giving us 4 total passes. If you take the two closest points to the two click points, then you might end up with the first point corresponding to a datum on one pass, and the second point corresponding to a datum on a completely different pass. The points in the sequence between these two points would then extend far beyond the curve. And, even if you got lucky and all the data points found were both on the same pass, that would only give you one of the passes, and we need to collect all passes.
For a while, I had a solution that worked much better. I calculated two new sequences representing the distance from each data point to each of the click points, then the approximate second derivative of that distance, looking for the inflection points of the distance from the click point over the data points. I reasoned that the inflection point meant that the points previous to the inflection were getting closer to the click point and the points after the inflection were getting further away from the click point. Doing this iteratively over the data points, I could group the curves as I came to them.
Perhaps some code is in order (this is C#, but don't worry about replying in kind, I'm capable of reading most languages):
static List<List<LatLngPoint>> GroupCurveSegments(List<LatLngPoint> dataPoints, LatLngPoint start, LatLngPoint end)
{
var withDistances = dataPoints.Select(p => new
{
ToStart = p.Distance(start),
ToEnd = p.Distance(end),
DataPoint = p
}).ToArray();
var set = new List<List<LatLngPoint>>();
var currentSegment = new List<LatLngPoint>();
for (int i = 0; i < withDistances.Length - 2; ++i)
{
var a = withDistances[i];
var b = withDistances[i + 1];
var c = withDistances[i + 2];
// the edge of the map can clip the data, so the continuity of
// the data is not exactly mapped to the continuity of the array.
var ab = b.DataPoint.RecordID - a.DataPoint.RecordID;
var bc = c.DataPoint.RecordID - b.DataPoint.RecordID;
var inflectStart = Math.Sign(a.ToStart - b.ToStart) * Math.Sign(b.ToStart - c.ToStart);
var inflectEnd = Math.Sign(a.ToEnd - b.ToEnd) * Math.Sign(b.ToEnd - c.ToEnd);
// if we haven't started a segment yet and we aren't obviously between segments
if ((currentSegment.Count == 0 && (inflectStart == -1 || inflectEnd == -1)
// if we have started a segment but we haven't changed directions away from it
|| currentSegment.Count > 0 && (inflectStart == 1 && inflectEnd == 1))
// and we're continuous on the data collection path
&& ab == 1
&& bc == 1)
{
// extend the segment
currentSegment.Add(b.DataPoint);
}
else if (
// if we have a segment collected
currentSegment.Count > 0
// and we changed directions away from one of the points
&& (inflectStart == -1
|| inflectEnd == -1
// or we lost data continuity
|| ab > 1
|| bc > 1))
{
// clip the segment and start a new one
set.Add(currentSegment);
currentSegment = new List<LatLngPoint>();
}
}
return set;
}
This worked great until we started advising the drivers to drive around 15MPH through turns (supposedly, it helps reduce sensor error. I'm personally not entirely convinced what we're seeing at higher speed is error, but I'm probably not going to win that argument). A car traveling at 15MPH is traveling at 22fps. Sampling this data at 5hz means that each data point is about four and a half feet apart. However, our GPS unit's precision is only about 5 feet. So, just the jitter of the GPS data itself could cause an inflection point in the data at such low speeds and high sample rates (technically, at this sample rate, you'd have to go at least 35MPH to avoid this problem, but it seems to work okay at 25MPH in practice).
Also, we're probably bumping up sampling rate to 10 - 15 Hz pretty soon. You'd need to drive at about 45MPH to avoid my inflection problem, which isn't safe on most of the curves of interest. My current procedure ends up splitting the data into dozens of subsegments, over road sections that I know had only 4 passes. One section that only had 300 data points came out to 35 subsegments. The rendering of the indication of the start and end of each pass (a small icon) indicated quite clearly that each real pass was getting chopped up into several pieces.
Where I'm Thinking of Going
Find the minimum distance of all points to both the start and end click points
Find all points that are within +10 feet of that distance.
Group each set of points by data continuity, i.e. each group should be continuous in the database, because more than one point on a particular pass could fall within the distance radius.
Take the data mid-point of each of those groups for each click point as the representative start and end for each pass.
Pair up points in the two sets per click point by those that would minimize the record index distance between each "start" and "end".
Halp?!
But I had tried this once before and it didn't work very well. Step #2 can return an unreasonably large number of points if the user doesn't click particularly close to where they intend. It can return too few points if the user clicks very, particularly close to where they intend. I'm not sure just how computationally intensive step #3 will be. And step #5 will fail if the driver were to drive over a particularly long curve and immediately turn around just after the start and end to perform the subsequent passes. We might be able to train the drivers to not do this, but I don't like taking chances on such things. So I could use some help figuring out how to clip and group this path that doubles back over itself into subsegments for passes over the curve.
Okay, so here is what I ended up doing, and it seems to work well for now. I like that it is a little simpler to follow than before. I decided that Step #4 from my question was not necessary. The exact point used as the start and end isn't critical, so I just take the first point that is within the desired radius of the first click point and the last point within the desired radius of the second point and take everything in the middle.
protected static List<List<T>> GroupCurveSegments<T>(List<T> dbpoints, LatLngPoint start, LatLngPoint end) where T : BBIDataPoint
{
var withDistances = dbpoints.Select(p => new
{
ToStart = p.Distance(start),
ToEnd = p.Distance(end),
DataPoint = p
}).ToArray();
var minToStart = withDistances.Min(p => p.ToStart) + 10;
var minToEnd = withDistances.Min(p => p.ToEnd) + 10;
bool startFound = false,
endFound = false,
oldStartFound = false,
oldEndFound = false;
var set = new List<List<T>>();
var cur = new List<T>();
foreach(var a in withDistances)
{
// save the previous values, because they
// impact the future values.
oldStartFound = startFound;
oldEndFound = endFound;
startFound =
!oldStartFound && a.ToStart <= minToStart
|| oldStartFound && !oldEndFound
|| oldStartFound && oldEndFound
&& (a.ToStart <= minToStart || a.ToEnd <= minToEnd);
endFound =
!oldEndFound && a.ToEnd <= minToEnd
|| !oldStartFound && oldEndFound
|| oldStartFound && oldEndFound
&& (a.ToStart <= minToStart || a.ToEnd <= minToEnd);
if (startFound || endFound)
{
cur.Add(a.DataPoint);
}
else if (cur.Count > 0)
{
set.Add(cur);
cur = new List<T>();
}
}
// if a data stream ended near the end of the curve,
// then the loop will not have saved it the pass.
if (cur.Count > 0)
{
cur = new List<T>();
}
return set;
}

C# - Finding the Boundaries of an Image (not the size)

I'm developing an application to split an image grid equally and center the images (based on their similarity). So far, I could manage to fix a grid of images with small sizes, but whenever I try a larger "sprite" size (100x100, for instance), I get Stack Overflow error.
Yes I'm using recursion, but whenever a pixel is checked, I set a boolean to deactivate it, copy it to a list and go on checking the others (in all directions), until the list is filled up with an image from the grid. I'm not sure if this is the best way since for each call, I call the same method 7 times (supposing there are 7 adjacent pixels which weren't checked yet)... until there are no pixels left to check, and I can move on to the next image in the grid.
I tried tracing where the error started happening, it was after checking more or less 1600 pixels and adding them to the List. MyPixel is a class which contains 4 variables: x(int), y(int), color (Color), and checked (bool)
public void processSprite(int i, int j)
{
//OOO
//OXO
//OOO
pixeltemp.Add(new MyPixel(imap.pixels[i, j].x, imap.pixels[i, j].y, imap.pixels[i, j].color));
imap.pixels[i, j].read = true;
//OOO
//OOX
//OOO
try
{
if (!imap.pixels[i + 1, j].read)
{
if (imap.pixels[i + 1, j].color.A == 0) //Found a Border
{
imap.pixels[i + 1, j].read = true;
}
else
{
processSprite(i + 1, j);
}
}
}
//... (code goes on)
}
pixeltemp is the temporary list of pixels which holds the image (List<MyPixel>)
imap contains the entire image (List<MyPixel>)
I guess it's not a memory problem since my app just takes about 16mb tops.
My question is, why do I have this "Stack overflow" error if it's not an infinite recursion? Is there an easier way to do this? I do think my code looks ugly, I just have no idea how to make it better.
Thanks in advance !
Stack overflows aren't caused by infinite recursion but by more recursion (or, rather, call stack) than the process can handle. In your case, each recursive call to processSprite is going to do the same number of recursive calls to processSprite. So in the worst-case scenario of 1600 pixels without finding a boundary, your call tree would look like this:
processSprite(0, j)
processSprite(1, j)
processSprite(2, j)
...
processSprite(1599, j) <-- That's 1600 call frames,
enough for an overflow.
You'll want to reorganize your algorithm into a linear loop that does a depth-first search, perhaps in a spiral pattern if you're wanting to to find a pixel that's closest to the starting point. I'm sure there are already other spiffy algorithms others have already developed to solve this problem.
Edit:
I think I understand better now the problem that you're trying to solve. It sounds like you have an image that may contain multiple image tiles surrounded by 0-alpha pixels and you want to find the bounding rectangles for each of these tiles. That looked like an interesting problem to solve, so I implemented it:
IEnumerable<Rectangle> FindImageTiles(Bitmap compositeImage)
{
var result = new List<Rectangle>();
// Scan for a non-empty region that hasn't already been "captured"
for (var x = 0; x < compositeImage.Width; x++)
{
for (var y = 0; y < compositeImage.Height; y++)
{
// Only process the pixel if we don't have a rectangle that
// already contains this and if it's not empty
if (!result.Any(r => r.Contains(x, y))
&& compositeImage.GetPixel(x, y).A != 0)
{
// Now that we've found a point, create a rectangle
// surrounding that point, then expand outward until
// we have a bounding rectangle that doesn't intersect
// with the tile
var rect = new Rectangle(x - 1, y - 1, 2, 2);
bool foundBounds = false;
while (!foundBounds)
{
var xRange = Enumerable.Range(rect.Left, rect.Right)
.Where(px => px >= 0 && px < compositeImage.Width);
var yRange = Enumerable.Range(rect.Top, rect.Bottom)
.Where(py => py >= 0 && py < compositeImage.Height);
// Adjust the top
if (rect.Top >= 0
&& xRange
.Select(bx => compositeImage.GetPixel(bx, rect.Top))
.Any(p => p.A != 0))
{
rect.Y--;
rect.Height++;
}
else if (rect.Bottom < compositeImage.Height
&& xRange
.Select(bx => compositeImage.GetPixel(bx, rect.Bottom))
.Any(p => p.A != 0))
{
rect.Height++;
}
else if (rect.Left >= 0
&& yRange
.Select(by => compositeImage.GetPixel(rect.Left, by))
.Any(p => p.A != 0))
{
rect.X--;
rect.Width++;
}
else if (rect.Right < compositeImage.Width
&& yRange
.Select(by => compositeImage.GetPixel(rect.Right, by))
.Any(p => p.A != 0))
{
rect.Width++;
}
else
{
foundBounds = true;
}
}
result.Add(rect);
}
}
}
return result;
}

Categories