I am trying to create a simulation of Traveling Salesman Problem and for this I am using GraphStream library. I have a class CreateGraph which is responsible for creating and maintaining graph. As a part of simulation I have to swap two cities (nodes in graph term) and for this what I am doing is
Get edges associated with node1 and store them in array named edge_1
Get edges associated with node2 and store them in array named edge_2
Remove duplicate edges from both the arrays and store them in array named common
Now remove both the nodes (this will also remove edges associated with them)
Create two nodes again with the same id's as previous ones
now iterate through edge_1 and add it with node2 and do same for node1 and edge_2
Now add common edges between them
Mostly everything is working fine but sometimes I am not getting all the edges associated with the node. See images below (give attention on blue nodes)
you can see the nodes and edges associated with them on bottom-left side in IDE.
Here is the CreateGraph class
package graph;
/**
* Created by deepanshu on 6/1/16.
*/
import org.graphstream.ui.view.Viewer;
import sa.City;
import sa.Tour;
import sa.TourManager;
import org.graphstream.graph.*;
import org.graphstream.graph.implementations.SingleGraph;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.Scanner;
import java.util.stream.Collectors;
public class CreateGraph {
protected String stylesheet =
"node { " +
"fill-color: green; text-color: blue; text-alignment: under;" +
"}" +
"edge { " +
"fill-color: red;" +
"}" +
"graph { "+
"fill-color: black;" +
" }";
private Graph graph;
public CreateGraph() {
graph = new SingleGraph("Simulated Annealing");
Viewer viewer = graph.display();
viewer.disableAutoLayout();
graph.addAttribute("ui.stylesheet", stylesheet);
graph.addAttribute("ui.quality");
graph.addAttribute("ui.antialias");
}
public void init() {
int totalNumberOfCities = TourManager.numberOfCities();
for (int i = 0; i < totalNumberOfCities; ++i) {
City city = TourManager.getCity(i);
int x = city.getX(), y = city.getY();
graph.addNode("" + x + ", " + y);
Node node = graph.getNode("" + x + ", " + y);
node.addAttribute("ui.label", node.getId());
node.setAttribute("x", x);
node.setAttribute("y", y);
// sleep();
}
}
public void set(Tour tour) {
ArrayList tour_real = tour.getTour();
int length = tour_real.size();
for (int i = 0; i < length - 1; ++i) {
City city_1 = (City)tour_real.get(i);
City city_2 = (City)tour_real.get(i + 1);
Node node_1 = graph.getNode("" + city_1.getX() + ", " + city_1.getY());
Node node_2 = graph.getNode("" + city_2.getX() + ", " + city_2.getY());
node_1.setAttribute("x", city_1.getX());
node_2.setAttribute("x", city_2.getX());
node_1.setAttribute("y", city_1.getY());
node_2.setAttribute("y", city_2.getY());
graph.addEdge(i + "", node_1, node_2, false);
// sleep();
}
}
private void addEdge(Iterator iterator, ArrayList<Edge> edges) {
while (iterator.hasNext()) {
edges.add((Edge)iterator.next());
}
}
private void addEdge(ArrayList<Edge> edges, Node node_1, Node node_2, ArrayList<Edge> changeEdge) {
for (Edge edge : edges) {
Node node0 = edge.getNode0();
Node node1 = edge.getNode1();
if (node_2.getId().equals(node1.getId())) {
graph.addEdge(edge.getId(), node_1, node0, false);
changeEdge.add(graph.getEdge(edge.getId()));
} else {
graph.addEdge(edge.getId(), node_1, node1, false);
changeEdge.add(graph.getEdge(edge.getId()));
}
}
}
private Node addNode(int x, int y) {
graph.addNode("" + x + ", " + y);
Node node_1 = graph.getNode("" + x + ", " + y);
node_1.setAttribute("x", x);
node_1.setAttribute("y", y);
node_1.addAttribute("ui.label", node_1.getId());
node_1.addAttribute("ui.style", "fill-color: blue; text-color: blue; text-alignment: under;");
return node_1;
}
private void removeCommon(ArrayList<Edge> edges_1, ArrayList<Edge> edges_2, ArrayList<Edge> common) {
for (Edge edge_1 : edges_1) {
common.addAll(edges_2.stream().filter(edge_2 -> edge_1.getId().equals(edge_2.getId())).map(edge_2 -> edge_1).collect(Collectors.toList()));
}
edges_1.removeAll(common);
edges_2.removeAll(common);
for (Edge edge : edges_1)
edge.addAttribute("ui.style", "fill-color: Green;");
for (Edge edge : edges_2)
edge.addAttribute("ui.style", "fill-color: Green;");
for (Edge edge : common)
edge.addAttribute("ui.style", "fill-color: Green;");
}
public void updateColor(ArrayList<Edge> changeEdge, Node node_1, Node node_2) {
for (Edge edge : changeEdge)
edge.addAttribute("ui.style", "fill-color: Red;");
node_1.addAttribute("ui.style", "fill-color: green; text-color: blue; text-alignment: under;");
node_2.addAttribute("ui.style", "fill-color: green; text-color: blue; text-alignment: under;");
}
public void update(City city_1, City city_2) {
ArrayList<Edge> changeEdge = new ArrayList<>();
int x_1 = city_1.getX(), y_1 = city_1.getY();
int x_2 = city_2.getX(), y_2 = city_2.getY();
Node node_1 = graph.getNode("" + x_1 + ", " + y_1);
Node node_2 = graph.getNode("" + x_2 + ", " + y_2);
ArrayList<Edge> edge_1 = new ArrayList<>();
ArrayList<Edge> edge_2 = new ArrayList<>();
ArrayList<Edge> common = new ArrayList<>();
addEdge(node_1.getEdgeIterator(), edge_1);
addEdge(node_2.getEdgeIterator(), edge_2);
removeCommon(edge_1, edge_2, common);
graph.removeNode(node_1);
graph.removeNode(node_2);
node_1 = addNode(x_2, y_2);
node_2 = addNode(x_1, y_1);
System.out.println("Node_1: " + node_1.toString());
System.out.println("Node_2: " + node_2.toString());
System.out.println("edge_1: " + edge_1.toString());
System.out.println("edge_2: " + edge_2.toString());
System.out.println("common: " + common.toString());
addEdge(edge_1, node_1, node_2, changeEdge);
addEdge(edge_2, node_2, node_1, changeEdge);
for (Edge edge : common) {
Node node1 = edge.getNode0();
Node node2 = edge.getNode1();
graph.addEdge(edge.getId(), node1, node2, false);
changeEdge.add(graph.getEdge(edge.getId()));
}
sleep();
updateColor(changeEdge, node_1, node_2);
sleep();
}
protected void sleep() {
try {
Thread.sleep(500);
Scanner scanner = new Scanner(System.in);
// scanner.nextLine();
} catch (Exception e) {
e.printStackTrace();
}
}
}
update method is where the swap of two nodes will occur.
Okay I found the bug myself. The bug was here
for (Edge edge : common) {
Node node1 = edge.getNode0();
Node node2 = edge.getNode1();
graph.addEdge(edge.getId(), node1, node2, false); <--- Replace it with
graph.addEdge(edge.getId(), node_1, node_2, false);
changeEdge.add(graph.getEdge(edge.getId()));
}
Actually I was assigning the local variable instead of real instance of nodes.
I am leaving this question here for future reference to anyone who wants to swap two nodes in GraphStream library.
Thank you for your time.
Related
I am implementing the breadth first search for an undirected graph using Java. However only one vertex get processed. Adjoining nodes for other vertices are returned as 0, even though they are properly added.
public class Graph {
class Vertex {
String label;
ArrayList<Vertex> adjNodeList;
boolean isVisited;
public Vertex(String label) {
this.label = label;
this.adjNodeList = new ArrayList<>();
this.isVisited = false;
}
}
ArrayList<Vertex> vertices;
public Graph() {
this.vertices = new ArrayList<>();
}
public void addNode(String label) {
this.vertices.add(new Vertex(label));
}
public void addEdge(String start, String end) {
this.vertices.forEach((e) -> {
if(e.label.equalsIgnoreCase(start)) {
e.adjNodeList.add(new Vertex(end));
}
});
}
public void printGraph() {
this.vertices.forEach((e -> {
System.out.print("Vertex - " + e.label + " -> ");
e.adjNodeList.forEach((v -> {
System.out.print(v.label);
}));
System.out.println();
}));
}
public void breadthFirstSearch() {
Queue<Vertex> theQueue = new LinkedList<>();
Vertex rv = this.vertices.get(0);
rv.isVisited = true;
theQueue.add(rv);
while(!theQueue.isEmpty()) {
Vertex vertex = theQueue.remove();
System.out.println("Processing - " + vertex.label);
System.out.println("List size - " + vertex.adjNodeList.size());
vertex.adjNodeList.forEach((e) -> {
if(!e.isVisited) {
e.isVisited = true;
theQueue.add(e);
System.out.println("Enqueued - " + e.label);
}
});
}
}
When graph is printed it shows all the edges correctly, but the BFS method can only process A and it's edges as seen below...
Vertex - A -> BC
Vertex - B -> G
Vertex - C -> D
Vertex - D -> E
Vertex - E ->
Vertex - G ->
Processing - A
List size - 2
Enqueued - B
Enqueued - C
Processing - B
List size - 0
Processing - C
List size - 0
even though they are properly added.
I assume by this that when you call addEdge - eg, by addEdge("A", "B"); - we can assume that you have already called addNode("A") and addNode("B").
If so, then the problem is in your addEdge method :
public void addEdge(String start, String end) {
this.vertices.forEach((e) -> {
if(e.label.equalsIgnoreCase(start)) {
e.adjNodeList.add(new Vertex(end));
}
});
}
So given addEdge("A", "B");, this code finds your already-added start vertex "A" - but then creates a new Vertex "B" WITHOUT looking for any that may have already been added. That new vertex has an empty adjNodeList, which will remain empty.
In other words, the Vertex "B" being referenced from "A" is a different instance from the Vertex "B" that is in the this.vertices.
So you should change addEdge (and, to do a sure job, addNode as well) to first look in this.vertices for an existing vertex.
eg something like this:
public Vertex fetchNode(String label) {
return this.vertices.stream()
.filter(v -> v.getLabel().equals(label))
.findAny()
.orElseGet( () -> {
Vertex newVertex = new Vertex(label));
this.vertices.add(newVertex);
return newVertex;
});
}
public void addEdge(String start, String end) {
this.vertices.forEach((e) -> {
if(e.label.equalsIgnoreCase(start)) {
e.adjNodeList.add(fetchNode(end));
}
});
}
I need to change attributes of nodes and edges over time. The time is split into timeperiods, every timeperiod looks the same: Check every node and edge for possible changes and edit attribute if necessary. Specifically there are numeric attributes and the size of a node and the width of an edge are based on the attributes value. Initially the graph displays correctly. The nodes and edges have the supposed size. But changing the attribute values dynamically over time does not change the elements sizes. How can I make sure, attribute changes also change the graphs visualisation?
As far as I understand the Graphstream Docs and tutorials there are sources, sinks and sipes (a pipe is both a source and a sink). Sources create events, sinks consumes them. I use the GridGenerator which is a source. I can add the graph as a sink and let the generator create the graph. I think, I have to add a sink to the graph then, because changing attributes of elements of the graph makes it a source. But what do I use as sink? graph.display() returns a Viewer but I can't add it as sink, it says it's not compatible with the arguments for graph.addSink(sink). Even though the Graphstream Docs says that a Viewer is a sink and that the Viewer gets added automatically as a sink. Why do I don't see changes in the UI then? I don't get it.
After generating the graph the nodes and edges get there attributes
public static void configureElements(Graph world) {
for (Node node : world.getEachNode()) {
double random = Math.random() * 100;
if (random < 20) {
// remove obstacles
world.removeNode(node)
} else if (random < 30) {
// node have rohstoffe
node.addAttribute("ui.class", "rohstoff");
node.addAttribute("ui.label", node.getId());
node.addAttribute("isRohstoff");
int capacity = (int) (Math.random() * maxCapacity);
node.addAttribute("capacity", capacity);ity);
// nodes size is based on capacity of rohstoffe
node.setAttribute("ui.size", node.getNumber("capacity") + 10);
} else if (random < 32) {
// node is a lager
node.addAttribute("ui.class", "lager");
node.addAttribute("ui.label", node.getId());
node.addAttribute("isLager");
node.addAttribute("lagerstand", 0);
// nodes size is based on capacity of the lager
node.setAttribute("ui.size", node.getNumber("lagerstand") + 10);
} else {
// normal node
node.addAttribute("isNode");
}
}
for (Edge edge : world.getEachEdge()) {
// add pheromones to edge
edge.addAttribute("pheromones", 0);
// edges size is based on number of pheromones
edge.setAttribute("ui.size", edge.getNumber("pheromones"));
}
}
Here I change the node attribute dynamically over time
public void dropRohstoff(Node node) {
int oldRohstoff = (int) node.getNumber("rohstoff");
int newRohstoff = oldRohstoff++;
node.setAttribute("rohstoff", newRohstoff);
world.nodeAttributeChanged(world.getId(), (long) world.getStep(), node.getId(),"rohstoff", oldRohstoff, newRohstoff);
}
public void pickRohstoff(Node node) {
int oldCapacity = (int) node.getNumber("capacity");
int newCapicity = oldCapacity++;
node.setAttribute("capacity", newCapicity);
world.nodeAttributeChanged(world.getId(), (long) world.getStep(), node.getId(), "capacity", oldCapacity, newCapicity);
}
Here the edge attributes
public void evaporateAll() {
for (Edge edge : world.getEachEdge()) {
Double oldEvaporateRate = edge.getNumber("pheromones");
Double newEvaporateRate = oldEvaporateRate * (1.0 - evaporateRate);
edge.setAttribute("pheromones", newEvaporateRate);
world.edgeAttributeChanged(world.getId(), (long) world.getStep(), edge.getId(), "pheromones", oldEvaporateRate, newEvaporateRate);
}
}
Does anybody know how do I have to add the sink? Or am I missing something else?
First, you can have a ViewerPipe with your viewer like that :
ViewerPipe pipeIn = viewer.newViewerPipe();
With that, you can add a sink to your graph :
pipeIn.addAttributeSink( graph );
pipeIn.pump();
Also, if you want to make a dynamic size, don't forget to add the css property to your nodes :
size-mode: dyn-size;
Here a minimal example for graphstream 1.3
public class Issue {
public static void main(String args[]) {
System.setProperty( "org.graphstream.ui.renderer", "org.graphstream.ui.j2dviewer.J2DGraphRenderer" );
new Issue();
}
protected boolean loop = true;
public Issue() {
Graph graph = new MultiGraph("main graph");
Viewer viewer = new Viewer(graph, Viewer.ThreadingModel.GRAPH_IN_ANOTHER_THREAD);
ViewerPipe pipeIn = viewer.newViewerPipe();
viewer.addView("view1", new J2DGraphRenderer());
graph.addAttribute("ui.antialias");
pipeIn.addAttributeSink( graph );
pipeIn.pump();
Node A = graph.addNode("A");
Node B = graph.addNode("B");
Node C = graph.addNode("C");
graph.addEdge("AB", "A", "B", true);
graph.addEdge("BC", "B", "C", true);
graph.addEdge("CA", "C", "A", true);
A.addAttribute("xyz", 0, 1, 0);
B.addAttribute("xyz", 1, 0, 0);
C.addAttribute("xyz", -1, 0, 0);
A.setAttribute("ui.label", "A");
B.setAttribute("ui.label", "B");
C.setAttribute("ui.label", "C");
graph.addAttribute("ui.stylesheet", styleSheet);
float color = 0;
float dir = 0.01f;
float size = 20f;
float sizeInc = 1f;
while( loop ) {
pipeIn.pump();
sleep( 40 );
A.setAttribute( "ui.size", size );
size += sizeInc;
if( size > 50 ) {
sizeInc = -1f; size = 50f;
} else if( size < 20 ) {
sizeInc = 1f; size = 20f;
}
System.out.println(size);
}
System.exit(0);
}
protected void sleep( long ms ) {
try { Thread.sleep( ms ) ; }
catch (InterruptedException e) { e.printStackTrace(); }
}
private String styleSheet =
"graph {"+
" canvas-color: white;"+
" fill-mode: gradient-radial;"+
" fill-color: white, #EEEEEE;"+
" padding: 60px;"+
" }"+
"node {"+
" size-mode: dyn-size;"+
" shape: circle;"+
" size: 20px;"+
" fill-mode: plain;"+
" fill-color: #CCC;"+
" stroke-mode: plain;"+
" stroke-color: black;"+
" stroke-width: 1px;"+
"}";
}
You can find an example here in graphstream 2.0 swing, here in graphstream 2.0 javafx or here in graphstream 1.3 (in scala).
I would like to draw an arrow in a JavaFX application, that rotates around an object that is bound to it (2 group objects that are connected with an arrow). To compute the correct angle I tried to compute it by substracting the two coordinates (end and start xy coordinates from the groups). But my arrow head will not move. Any ideas what I am doing wrong?
// controller code:
PlaceIcon startIconGroup = new PlaceIcon();
startIconGroup.setLayoutX(100);
startIconGroup.setLayoutY(120);
startIconGroup.addEventHandler(MouseEvent.MOUSE_CLICKED, onClickEventHandler); // for dragging
startIconGroup.addEventHandler(MouseEvent.MOUSE_DRAGGED, onDragEventHandler);
startIconGroup.addEventHandler(MouseEvent.MOUSE_PRESSED, onPressEventHandler);
workplace.getChildren().add(startIconGroup); // workplace is the pane were the nodes are shown
PlaceIcon endIconGroup = new PlaceIcon();
endIconGroup.setLayoutX(100);
endIconGroup.setLayoutY(120);
endIconGroup.addEventHandler(MouseEvent.MOUSE_CLICKED, onClickEventHandler);
endIconGroup.addEventHandler(MouseEvent.MOUSE_DRAGGED, onDragEventHandler);
endIconGroup.addEventHandler(MouseEvent.MOUSE_PRESSED, onPressEventHandler);
endIconGroup.getChildren().add(startIconGroup);
RelationIcon relationIcon = new RelationIcon();
relationIcon.setStartNode(startNode);
relationIcon.setEndNode(endNode);
...
EventHandler<MouseEvent> onDragEventHandler = new EventHandler<MouseEvent>() {
/**
* This method is used to compute the new position of an node. Therefore, the
* position in pixels (x/yLayout) is incremented by coordinate change of mouse
* movement.
*/
#Override
public void handle(MouseEvent event) {
Group clickedElement = (Group) event.getSource();
// Node node = clickedElement.getChildren().get(0);
double offsetX = event.getSceneX() - mousePosition.get().getX();
double offsetY = event.getSceneY() - mousePosition.get().getY();
clickedElement.setLayoutX(clickedElement.getLayoutX() + offsetX);
clickedElement.setLayoutY(clickedElement.getLayoutY() + offsetY);
mousePosition.set(new Point2D(event.getSceneX(), event.getSceneY()));
eventlog.setText("Move "+ clickedElement + " to x=" + clickedElement.getLayoutX() + " y="
+ clickedElement.getLayoutY());
}
};
And here is the PlaceIconClass
public class RelationIcon extends Group {
Line line;
Polygon arrowHead;
Group startNode;
Group endNode;
public RelationIcon() {
//draw arrow line
line = new Line();
// draw arrow head
arrowHead = new Polygon();
arrowHead.getPoints().addAll(new Double[]{
-10.0, -20.0,
10.0, -20.0,
0.0, 0.0});
// merge into a group
this.getChildren().addAll(line, arrowHead);
}
public Group getStartNode() {
return startNode;
}
public void setStartNode(Group startNode) {
this.startNode = startNode;
line.startXProperty().bind(this.startNode.layoutXProperty());
line.startYProperty().bind(this.startNode.layoutYProperty());
}
public Group getEndNode() {
return endNode;
}
public void setEndNode(Group endNode) {
this.endNode = endNode;
line.endXProperty().bind(this.endNode.layoutXProperty());
line.endYProperty().bind(this.endNode.layoutYProperty());
arrowHead.layoutXProperty().bind(this.endNode.layoutXProperty());
arrowHead.layoutYProperty().bind(this.endNode.layoutYProperty());
arrowHead.rotateProperty().bind(this.endNode.layoutXProperty()); // THIS WORKS
arrowHead.rotateProperty().bind(Bindings.createDoubleBinding(() -> {
double x = line.endXProperty().getValue() - line.startXProperty().getValue();
double y = line.endYProperty().getValue() - line.startYProperty().getValue();
System.out.println(x+" "+y);
double a = Math.atan2(x, y);
return Math.toDegrees(a);
})); // THIS WORKS NOT
}
Thank you for your help!
I'am new new to JavaFX, and I got a problem. I'm trying to move Nodes with UP,DOWN,LEFT,RIGHT, if I just move my "Player" node, it's work perfectly, but I wanna make a game like sokoban, so my Player node can move "Crate" node, the problem is if I press the RIGHT next to the Crate, it swap the places, but not working correctly here is the moving method :
private void lepes(int i, int j, int target_i, int target_j){
int ai, aj;
int bi, bj;
ai = i;
aj = j;
bi = target_i;
bj = target_j;
MyNode nodeA = playfield[ai][aj];
nodeA.toFront();
MyNode nodeB = playfield[bi][bj];
nodeB.toFront();
playfield[ai][aj] = nodeB;
playfield[bi][bj] = nodeA;
Path pathA = new Path();
pathA.getElements().add (new MoveTo ( nodeA.getTranslateX() + nodeA.getBoundsInParent().getWidth() / 2.0, nodeA.getTranslateY() + nodeA.getBoundsInParent().getHeight() / 2.0));
pathA.getElements().add (new LineTo( nodeB.getTranslateX() + nodeB.getBoundsInParent().getWidth() / 2.0, nodeB.getTranslateY() + nodeB.getBoundsInParent().getHeight() / 2.0));
PathTransition pathTransitionA = new PathTransition();
pathTransitionA.setDuration(Duration.millis(1));
pathTransitionA.setNode( nodeA);
pathTransitionA.setPath(pathA);
pathTransitionA.play();
Path pathB = new Path();
pathB.getElements().add (new MoveTo ( nodeB.getTranslateX() + nodeB.getBoundsInParent().getWidth() / 2.0, nodeB.getTranslateY() + nodeB.getBoundsInParent().getHeight() / 2.0));
pathB.getElements().add (new LineTo( nodeA.getTranslateX() + nodeA.getBoundsInParent().getWidth() / 2.0, nodeA.getTranslateY() + nodeA.getBoundsInParent().getHeight() / 2.0));
PathTransition pathTransitionB = new PathTransition();
pathTransitionB.setDuration(Duration.millis(1));
pathTransitionB.setNode( nodeB);
pathTransitionB.setPath(pathB);
pathTransitionB.play();
pathTransitionA.setOnFinished( new EventHandler<ActionEvent>() {
#Override
public void handle(ActionEvent event) {
if( (pathTransitionB.getStatus() == Status.RUNNING))
return;
}
});
pathTransitionB.setOnFinished( new EventHandler<ActionEvent>() {
#Override
public void handle(ActionEvent event) {
if( (pathTransitionA.getStatus() == Status.RUNNING))
return;
}
});
}
and here is the eventHandler
scene.setOnKeyPressed(new EventHandler<KeyEvent>() {
#Override
public void handle(KeyEvent event) {
if (event.getCode()==KeyCode.RIGHT) {
if(jatekosX < 8 && playfield[jatekosX+1][jatekosY].getHisNumber() == 2){
lepes(jatekosX, jatekosY, jatekosX+1, jatekosY);
jatekosX++;
lepesek++;
}
else if(jatekosX < 8 && playfield[jatekosX+1][jatekosY].getHisNumber() == 1 ){
lepes(jatekosX+1, jatekosY, jatekosX+2, jatekosY);
lepes(jatekosX, jatekosY, jatekosX+1, jatekosY);
jatekosX++;
lepesek++;
}
System.out.println("Jobbra");
System.out.println("X: " + jatekosX + "Y: " + jatekosY + " lépés: " + lepesek + "\n");
}
}
});
Here is the picture next to crate node, https://postimg.org/image/7qkqxnxs5/
And here is after a Right step, https://postimg.org/image/t1ib1xfwl/
I Don't know what causes this.
I have writen a program in Processing to implement the Bentley-Ottmann algorithm for the line segment intersection problem.
The program returns a set of points but it does not find all the intersections. Can anyone tell me please what have I done wrong?
An ArrayList holds the active line segments that intersect the sweep line.
For each endpoint I store its x position and the Line2D it belongs to, at an TreeMap(Double, List(Line2D)) structure.
I am holding a List of Lines2D (TreeMap(Double, List(Line2D) ) because if I find an intersection I report its x position and the two lines that intersect.
I think I followed all the steps required by the algorithm. I've studied from the "Computational Geometry - Algorithms and Applications", and also a pdf named "Computing intersections in a set of line segments: the Bentley-Ottmann algorithm" from Michiel Smid.
Although, I am not taking into account the restrictions about the vertical lines, intersection with three segments etc. most of the times the requirements are met but still the results are not the expected ones.
This is an example
This is my code
import java.util.*;
import java.awt.*;
import java.awt.geom.*;
import java.util.List;
import java.util.concurrent.TimeUnit;
Algorithm algorithm = new Algorithm();
List<Line2D> initialList = new ArrayList<Line2D>();
List<Double> xStructure = new ArrayList<Double>();
Set<Point2D> intersections = new HashSet<Point2D>();
void setup() {
size(640, 480);
initialList = algorithm.createListOfRandomLines(5);
long start = System.currentTimeMillis();
intersections = algorithm.runBentleyOttmann(initialList);
long stop = System.currentTimeMillis() - start;
System.out.println(String.format("Execution Time: %f sec", (float)TimeUnit.MILLISECONDS.toSeconds(stop)));
}
void draw() {
background(255);
strokeWeight(1);
stroke(0);
for(Line2D l : initialList)
line((float)l.getX1(), (float)l.getY1(), (float)l.getX2(), (float)l.getY2());
strokeWeight(5);
stroke(255,0,0);
for(Point2D p : intersections)
point((float)p.getX(), (float)p.getY());
}
class Algorithm {
TreeMap<Double, List<Line2D>> xStructure;
Double slPosition;
List<Line2D> slStatus; // Y-structure
Set<Point2D> intersectionPoints;
public Algorithm() {
intersectionPoints = new HashSet<Point2D>();
slPosition = Double.MIN_VALUE;
slStatus = new ArrayList<Line2D>();
xStructure = new TreeMap<Double, List<Line2D>>();
}
public Set<Point2D> runBentleyOttmann(List<Line2D> list) {
//System.out.println("DEBUG INFO: runBentleyOttmann got input a list of " + list.size() + " line segments.");
xStructure = createMapOfSortedEndpointsAndCorespondingLineSegment(list);
//System.out.println("DEBUG INFO: runBentleyOttmann x-structure now contains the " + xStructure.size() + " endpoints.");
//System.out.println("DEBUG INTO: runBentleyOttmann sweep line is at position " + slPosition);
//System.out.println("DEBUG INFO: runBentleyOttmann begins while loop");
while (!xStructure.isEmpty()) {
Map.Entry<Double, List<Line2D>> entry = xStructure.pollFirstEntry();
Double min = entry.getKey();
slPosition = min;
List<Line2D> lines = entry.getValue();
//System.out.println("-----DEBUG INFO: runBentleyOttmann let min be the minimum element of x-structure " + min);
//System.out.println("-----DEBUG INFO: runBentleyOttmann delete min from the x-structure");
//System.out.println("-----DEBUG INFO: runBentleyOttmann checks endpoint type");
if (checkEndpointType(lines) == EndpointType.LEFT) {
//System.out.println("----------DEBUG INFO: Endpoint Type LEFT");
handleLeftEndpoint(lines);
}
else if (checkEndpointType(lines) == EndpointType.RIGHT) {
//System.out.println("----------DEBUG INFO: Endpoint Type RIGHT");
handleRightEndpoint(lines);
}
else if (checkEndpointType(lines) == EndpointType.INTERSECTION) {
//System.out.println("----------DEBUG INFO: Endpoint Type INTERSECTION");
handleIntersectionEndpoint(lines);
}
}
//System.out.println("DEBUG INFO: runBentleyOttmann ends while loop");
return intersectionPoints;
}
public void handleIntersectionEndpoint(List<Line2D> lines) {
Line2D l1 = slStatus.get(slStatus.indexOf(lines.get(0)));
Line2D l2 = slStatus.get(slStatus.indexOf(lines.get(1)));
if (slStatus.indexOf(l1) > slStatus.indexOf(l2)) {
Line2D ltemp = slStatus.get(slStatus.indexOf(lines.get(0)));
l1 = l2;
l2 = ltemp;
}
Line2D prev;
Line2D next;
if (slStatus.indexOf(l1) > 0) {
prev = getAboveLine(l1);
if (prev != null)
if (l2.intersectsLine(prev)) {
Point2D ip = findIntersectionPoint(l2, prev);
if (ip.getX() > slPosition) {
intersectionPoints.add(findIntersectionPoint(l2, prev));
List<Line2D> tempList = new ArrayList<Line2D>();
tempList.add(l2);
tempList.add(prev);
xStructure.put(ip.getX(), tempList);
}
}
}
if (slStatus.indexOf(l2) < slStatus.size()-1) {
next = getBelowLine(l2);
if (next != null)
if (l1.intersectsLine(next)) {
Point2D ip = findIntersectionPoint(l1, next);
if (ip.getX() > slPosition) {
intersectionPoints.add(findIntersectionPoint(l1, next));
List<Line2D> tempList = new ArrayList<Line2D>();
tempList.add(l1);
tempList.add(next);
xStructure.put(ip.getX(), tempList);
}
}
}
System.out.println(slStatus.indexOf(l1) + " " + slStatus.indexOf(l2));
}
public void handleLeftEndpoint(List<Line2D> lines) {
//System.out.println("HANDLE LEFT ENDPOINT started");
slStatus.add(lines.get(0));
//System.out.println("Line [X1=" + lines.get(0).getX1() + ", Y1=" + lines.get(0).getY1() + ", X2=" + lines.get(0).getX2() + ", Y2=" + lines.get(0).getY2() + "] added to Y-Structure.");
Collections.sort(slStatus, new YComparator());
//System.out.println("Y-Structure is sorted by Y endpoint");
Line2D above = getAboveLine(lines.get(0));
if (above != null)
if (lines.get(0).intersectsLine(above)) {
Point2D ip = findIntersectionPoint(lines.get(0), above);
if (ip.getX() > slPosition) {
intersectionPoints.add(findIntersectionPoint(lines.get(0), above));
List<Line2D> tempList = new ArrayList<Line2D>();
tempList.add(lines.get(0));
tempList.add(above);
xStructure.put(ip.getX(), tempList);
}
}
Line2D below = getBelowLine(lines.get(0));
if (below != null)
if (lines.get(0).intersectsLine(below)) {
Point2D ip = findIntersectionPoint(lines.get(0), below);
if (ip.getX() > slPosition) {
intersectionPoints.add(findIntersectionPoint(lines.get(0), below));
List<Line2D> tempList = new ArrayList<Line2D>();
tempList.add(lines.get(0));
tempList.add(below);
xStructure.put(ip.getX(), tempList);
}
}
//System.out.println("HANDLE LEFT ENDPOINT finished");
}
public void handleRightEndpoint(List<Line2D> lines) {
//System.out.println("HANDLE RIGHT ENDPOINT started");
Line2D above = getAboveLine(lines.get(0));
Line2D below = getBelowLine(lines.get(0));
if (above != null && below != null)
if (above.intersectsLine(below)){
Point2D ip = findIntersectionPoint(lines.get(0), below);
if (ip.getX() > slPosition)
intersectionPoints.add(findIntersectionPoint(lines.get(0), below));
}
slStatus.remove(lines.get(0));
//System.out.println("Line [X1=" + lines.get(0).getX1() + ", Y1=" + lines.get(0).getY1() + ", X2=" + lines.get(0).getX2() + ", Y2=" + lines.get(0).getY2() + "] deleted from Y-Structure.");
}
public Line2D getAboveLine(Line2D line) {
if (slStatus.indexOf(line) > 0) {
Line2D a = slStatus.get(slStatus.indexOf(line)-1);
//System.out.println("getAboveLine returns line [X1=" + a.getX1() + ", Y1=" + a.getY1() + ", X2=" + a.getX2() + ", Y2=" + a.getY2() + "]");
return a;
}
//System.out.println("getAboveLine returns no line");
return null;
}
public Line2D getBelowLine(Line2D line) {
if (slStatus.indexOf(line) < slStatus.size()-1) {
Line2D b = slStatus.get(slStatus.indexOf(line)+1);
//System.out.println("getBelowLine returns line [X1=" + b.getX1() + ", Y1=" + b.getY1() + ", X2=" + b.getX2() + ", Y2=" + b.getY2() + "]");
//System.out.println("getAboveLine returns no line");
return b;
}
return null;
}
public EndpointType checkEndpointType(List<Line2D> lines) {
if (lines.size() == 2)
return EndpointType.INTERSECTION;
if (slStatus.contains(lines.get(0)))
return EndpointType.RIGHT;
if (!slStatus.contains(lines.get(0)))
return EndpointType.LEFT;
return null;
}
public List<Line2D> sortEndpointsByXCoordinate(List<Line2D> inputList) {
List<Line2D> returnList = new ArrayList(inputList);
Collections.sort(returnList, new XComparator());
return returnList;
}
public TreeMap<Double, List<Line2D>> createMapOfSortedEndpointsAndCorespondingLineSegment(List<Line2D> inputList) {
TreeMap returnTreeMap = new TreeMap<Double, Line2D>();
for (Line2D l : inputList) {
List<Line2D> tempList = new ArrayList<Line2D>();
tempList.add(l);
returnTreeMap.put(l.getX1(), tempList);
returnTreeMap.put(l.getX2(), tempList);
}
return returnTreeMap;
}
public List<Double> createListOfSortedEndpoints(List<Line2D> inputList) {
List<Double> returnList = new ArrayList<Double>();
for (Line2D l : inputList) {
returnList.add(l.getBounds().getX());
returnList.add(l.getBounds().getY());
}
Collections.sort(returnList);
//System.out.println("DEBUG INFO: createListOfSortedEndpoints returns " + returnList.size() + " endpoints");
return returnList;
}
public List<Line2D> createListOfRandomLines(int size) {
Set<Line2D> hashset = new HashSet<Line2D>();
Random random = new Random();
while (hashset.size() != size) {
Line2D line = new Line2D.Double();
line.setLine(random.nextInt(width-1)+1, random.nextInt(height-1)+1, random.nextInt(width-1)+1, random.nextInt(height-1)+1);
hashset.add(line);
}
List<Line2D> returnList = new ArrayList(hashset);
return returnList;
}
public Point2D findIntersectionPoint(Line2D l1, Line2D l2) {
Double p0_x = l1.getX1();
Double p0_y = l1.getY1();
Double p1_x = l1.getX2();
Double p1_y = l1.getY2();
Double p2_x = l2.getX1();
Double p2_y = l2.getY1();
Double p3_x = l2.getX2();
Double p3_y = l2.getY2();
Double s1_x = p1_x - p0_x;
Double s1_y = p1_y - p0_y;
Double s2_x = p3_x - p2_x;
Double s2_y = p3_y - p2_y;
Double s, t;
s = (-s1_y * (p0_x - p2_x) + s1_x * (p0_y - p2_y)) / (-s2_x * s1_y + s1_x * s2_y);
t = ( s2_x * (p0_y - p2_y) - s2_y * (p0_x - p2_x)) / (-s2_x * s1_y + s1_x * s2_y);
Double i_x, i_y;
i_x = p0_x + (t * s1_x);
i_y = p0_y + (t * s1_y);
Point2D p = new Point2D.Double(i_x, i_y);
return p;
}
}
class XComparator implements Comparator<Line2D>{
public int compare(Line2D l1, Line2D l2){
return new Double(l1.getBounds().getX()).compareTo(l2.getBounds().getX());
}
}
class YComparator implements Comparator<Line2D>{
public int compare(Line2D l1, Line2D l2){
return new Double(l1.getBounds().getY()).compareTo(l2.getBounds().getY());
}
}
enum EndpointType {
LEFT, RIGHT, INTERSECTION
};
I would recommend considering the following Java implementation: https://github.com/stanislav-antonov/bentley-ottmann
It's clean and minimalistic enough, but still handles all the major edge cases.